[
  {
    "path": ".flake8",
    "content": "[flake8]\nmax-line-length = 80\n"
  },
  {
    "path": ".github/workflows/pypi.yml",
    "content": "name: Publish to PyPI\n\non:\n  release:\n    types:\n      - published\n  workflow_dispatch:\n    inputs:\n      twine_verbose:\n        description: 'Enable Twine verbose mode'\n        required: true\n        type: boolean\n\njobs:\n  pypi-publish:\n    name: upload release to PyPI\n    runs-on: ubuntu-latest\n    environment:\n      name: pypi\n      url: https://pypi.org/p/pyftdi\n    permissions:\n      id-token: write\n    strategy:\n      matrix:\n        python-version: ['3.13']\n    steps:\n\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install setuptools wheel\n\n    - name: Build package\n      run: |\n        python setup.py bdist_wheel\n\n    - name: Publish package distributions to PyPI\n      uses: pypa/gh-action-pypi-publish@release/v1\n      with:\n          verbose: ${{ inputs.twine_verbose }}\n"
  },
  {
    "path": ".github/workflows/pythonchecksyntax.yml",
    "content": "name: Python syntax tests\n# check that there is no import issues with tool suite\n\non:\n  push:\n  pull_request:\n    types: [assigned, opened, synchronize, reopened]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install -r requirements.txt\n        pip install -r test-requirements.txt\n\n    - name: Check style\n      run: |\n        python setup.py check_style\n\n    - name: Linter\n      run: |\n        pylint --disable=fixme --disable=duplicate-code \\\n          $(git ls-files '*.py')\n\n    - name: Install package\n      run: |\n        python setup.py install\n\n    - name: Run tests\n      run: |\n        python pyftdi/tests/toolsimport.py\n"
  },
  {
    "path": ".github/workflows/pythonmocktests.yml",
    "content": "name: Python USB mock tests\n\non:\n  push:\n  pull_request:\n    types: [assigned, opened, synchronize, reopened]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install -r requirements.txt\n        pip install setuptools wheel ruamel.yaml\n\n    - name: Install package\n      run: |\n        python setup.py install\n\n    - name: Run Mock tests\n      env:\n        FTDI_LOGLEVEL: WARNING\n        FTDI_DEBUG: on\n      run: |\n        python pyftdi/tests/mockusb.py\n\n    - name: Run GPIO tests\n      env:\n        FTDI_LOGLEVEL: WARNING\n        FTDI_DEBUG: on\n        FTDI_VIRTUAL: on\n      run: |\n        python pyftdi/tests/gpio.py\n\n    - name: Run EEPROM tests\n      env:\n        FTDI_LOGLEVEL: WARNING\n        FTDI_DEBUG: on\n        FTDI_VIRTUAL: on\n      run: |\n        python pyftdi/tests/eeprom_mock.py\n"
  },
  {
    "path": ".github/workflows/pythonpackage.yml",
    "content": "name: Python package\n\non:\n  push:\n  pull_request:\n    types: [assigned, opened, synchronize, reopened]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install -r requirements.txt\n        pip install setuptools wheel sphinx sphinx_rtd_theme sphinx_autodoc_typehints\n\n    - name: Build package\n      run: |\n        python setup.py bdist\n        python setup.py sdist bdist_wheel\n\n    - name: Build documentation\n      run: |\n        mkdir doc\n        cd doc\n        sphinx-build -W -b html ../pyftdi/doc .\n"
  },
  {
    "path": ".gitignore",
    "content": "*.egg-info\n*.pyc\n*.pyo\n**/.DS_Store\nMANIFEST\npyusb*\ndist/\nbuild/\nsphinx/\n.vscode/\n.venv/\n"
  },
  {
    "path": ".pylintrc",
    "content": "[MASTER]\n\ninit-hook='import sys; sys.path.append(\".\")'\n\n[MESSAGES CONTROL]\n\ndisable=\n    too-few-public-methods,\n    too-many-arguments,\n    too-many-branches,\n    too-many-instance-attributes,\n    too-many-lines,\n    too-many-locals,\n    too-many-nested-blocks,\n    too-many-positional-arguments,\n    too-many-public-methods,\n    too-many-return-statements,\n    too-many-statements,\n    unspecified-encoding\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2008-2025 Emmanuel Blot <emmanuel.blot@free.fr>\nAll Rights Reserved.\n\nSPDX-License-Identifier: BSD-3-Clause\n\ni.e.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the author nor the names of its contributors may\n      be used to endorse or promote products derived from this software\n      without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL NEOTION BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,\nOR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\nEVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "exclude README.md\ninclude pyftdi/doc/images/*.png"
  },
  {
    "path": "README.md",
    "content": "[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://vshymanskyy.github.io/StandWithUkraine)\n\n# PyFtdi\n\n![Python package](https://github.com/eblot/pyftdi/actions/workflows/pythonpackage.yml/badge.svg)\n![Mock tests](https://github.com/eblot/pyftdi/actions/workflows/pythonmocktests.yml/badge.svg)\n![Syntax tests](https://github.com/eblot/pyftdi/actions/workflows/pythonchecksyntax.yml/badge.svg)\n[![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://vshymanskyy.github.io/StandWithUkraine)\n\n[![PyPI](https://img.shields.io/pypi/v/pyftdi.svg?maxAge=2592000)](https://pypi.org/project/pyftdi/)\n[![Python Versions](https://img.shields.io/pypi/pyversions/pyftdi.svg)](https://pypi.org/project/pyftdi/)\n[![Downloads](https://img.shields.io/pypi/dm/pyftdi.svg)](https://pypi.org/project/pyftdi/)\n\n## Documentation\n\nPyFtdi documentation is available from https://eblot.github.io/pyftdi/\n\n## Overview\n\nPyFtdi aims at providing a user-space driver for popular FTDI devices,\nimplemented in pure Python language.\n\nSuported FTDI devices include:\n\n* UART and GPIO bridges\n\n  * FT232R (single port, 3Mbps)\n  * FT230X/FT231X/FT234X (single port, 3Mbps)\n\n* UART, GPIO and multi-serial protocols (SPI, I2C, JTAG) bridges\n\n  * FT2232C/D (dual port, clock up to 6 MHz)\n  * FT232H (single port, clock up to 30 MHz)\n  * FT2232H (dual port, clock up to 30 MHz)\n  * FT4232H (quad port, clock up to 30 MHz)\n  * FT4232HA (quad port, clock up to 30 MHz)\n\n## Features\n\nPyFtdi currently supports the following features:\n\n* UART/Serial USB converter, up to 12Mbps (depending on the FTDI device\n  capability)\n* GPIO/Bitbang support, with 8-bit asynchronous, 8-bit synchronous and\n  8-/16-bit MPSSE variants\n* SPI master, with simultanous GPIO support, up to 12 pins per port,\n  with support for non-byte sized transfer\n* I2C master, with simultanous GPIO support, up to 14 pins per port\n* Basic JTAG master capabilities\n* EEPROM support (some parameters cannot yet be modified, only retrieved)\n* Experimental CBUS support on selected devices, 4 pins per port\n\n## Supported host OSes\n\n* macOS\n* Linux\n* FreeBSD\n* Windows, although not officially supported\n\n## License\n\n`SPDX-License-Identifier: BSD-3-Clause`\n\n## Warnings\n\n### Python support\n\nPyFtdi requires Python 3.9+.\n\nSee `pyftdi/doc/requirements.rst` for more details.\n"
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-architect"
  },
  {
    "path": "pyftdi/INSTALL",
    "content": "Please read pyftdi/README.rst for installation instructions.\n"
  },
  {
    "path": "pyftdi/__init__.py",
    "content": "# Copyright (c) 2010-2025 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2010-2016, Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=missing-docstring\n\n__version__ = '0.57.1'\n__title__ = 'PyFtdi'\n__description__ = 'FTDI device driver (pure Python)'\n__uri__ = 'http://github.com/eblot/pyftdi'\n__doc__ = __description__ + ' <' + __uri__ + '>'\n__author__ = 'Emmanuel Blot'\n# For all support requests, please open a new issue on GitHub\n__email__ = 'emmanuel.blot@free.fr'\n__license__ = 'BSD-3-Clause'\n__copyright__ = 'Copyright (c) 2011-2025 Emmanuel Blot'\n\n\nfrom logging import WARNING, NullHandler, getLogger\n\n\nclass FtdiLogger:\n\n    log = getLogger('pyftdi')\n    log.addHandler(NullHandler())\n    log.setLevel(level=WARNING)\n\n    @classmethod\n    def set_formatter(cls, formatter):\n        handlers = list(cls.log.handlers)\n        for handler in handlers:\n            handler.setFormatter(formatter)\n\n    @classmethod\n    def get_level(cls):\n        return cls.log.getEffectiveLevel()\n\n    @classmethod\n    def set_level(cls, level):\n        cls.log.setLevel(level=level)\n"
  },
  {
    "path": "pyftdi/bin/ftconf.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Simple FTDI EEPROM configurator.\n\"\"\"\n\n# Copyright (c) 2019-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nfrom argparse import ArgumentParser, FileType\nfrom io import StringIO\nfrom logging import Formatter, StreamHandler, DEBUG, ERROR\nfrom sys import exit as sys_exit, modules, stderr, stdout\nfrom textwrap import fill\nfrom traceback import format_exc\nfrom pyftdi import FtdiLogger\nfrom pyftdi.eeprom import FtdiEeprom\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.misc import add_custom_devices, hexdump\n\n# pylint: disable=too-many-locals\n# pylint: disable=too-many-branches\n# pylint: disable=too-many-statements\n\n\ndef main():\n    \"\"\"Main routine\"\"\"\n    debug = False\n    try:\n        argparser = ArgumentParser(description=modules[__name__].__doc__)\n        argparser.add_argument('device', nargs='?', default='ftdi:///?',\n                               help='serial port device name')\n\n        files = argparser.add_argument_group(title='Files')\n        files.add_argument('-i', '--input', type=FileType('rt'),\n                           help='input ini file to load EEPROM content')\n        files.add_argument('-l', '--load', default='all',\n                           choices=('all', 'raw', 'values'),\n                           help='section(s) to load from input file')\n        files.add_argument('-o', '--output',\n                           help='output ini file to save EEPROM content')\n        files.add_argument('-V', '--virtual', type=FileType('r'),\n                           help='use a virtual device, specified as YaML')\n\n        device = argparser.add_argument_group(title='Device')\n        device.add_argument('-P', '--vidpid', action='append',\n                            help='specify a custom VID:PID device ID '\n                                 '(search for FTDI devices)')\n        device.add_argument('-M', '--eeprom',\n                            help='force an EEPROM model')\n        device.add_argument('-S', '--size', type=int,\n                            choices=FtdiEeprom.eeprom_sizes,\n                            help='force an EEPROM size')\n\n        fmt = argparser.add_argument_group(title='Format')\n        fmt.add_argument('-x', '--hexdump', action='store_true',\n                         help='dump EEPROM content as ASCII')\n        fmt.add_argument('-X', '--hexblock', type=int,\n                         help='dump EEPROM as indented hexa blocks')\n\n        config = argparser.add_argument_group(title='Configuration')\n        config.add_argument('-s', '--serial-number',\n                            help='set serial number')\n        config.add_argument('-m', '--manufacturer',\n                            help='set manufacturer name')\n        config.add_argument('-p', '--product',\n                            help='set product name')\n        config.add_argument('-c', '--config', action='append',\n                            help='change/configure a property as key=value '\n                                 'pair')\n        config.add_argument('--vid', type=lambda x: int(x, 16),\n                            help='shortcut to configure the USB vendor ID')\n        config.add_argument('--pid', type=lambda x: int(x, 16),\n                            help='shortcut to configure the USB product ID')\n\n        action = argparser.add_argument_group(title='Action')\n        action.add_argument('-e', '--erase', action='store_true',\n                            help='erase the whole EEPROM content')\n        action.add_argument('-E', '--full-erase', action='store_true',\n                            default=False,\n                            help='erase the whole EEPROM content, including '\n                                 'the CRC')\n        action.add_argument('-u', '--update', action='store_true',\n                            help='perform actual update, use w/ care')\n\n        extra = argparser.add_argument_group(title='Extras')\n        extra.add_argument('-v', '--verbose', action='count', default=0,\n                           help='increase verbosity')\n        extra.add_argument('-d', '--debug', action='store_true',\n                           help='enable debug mode')\n        args = argparser.parse_args()\n        debug = args.debug\n\n        if not args.device:\n            argparser.error('Serial device not specified')\n\n        loglevel = max(DEBUG, ERROR - (10 * args.verbose))\n        loglevel = min(ERROR, loglevel)\n        if debug:\n            formatter = Formatter('%(asctime)s.%(msecs)03d %(name)-20s '\n                                  '%(message)s', '%H:%M:%S')\n        else:\n            formatter = Formatter('%(message)s')\n        FtdiLogger.set_formatter(formatter)\n        FtdiLogger.set_level(loglevel)\n        FtdiLogger.log.addHandler(StreamHandler(stderr))\n\n        if args.virtual:\n            # pylint: disable=import-outside-toplevel\n            from pyftdi.usbtools import UsbTools\n            # Force PyUSB to use PyFtdi test framework for USB backends\n            UsbTools.BACKENDS = ('pyftdi.tests.backend.usbvirt', )\n            # Ensure the virtual backend can be found and is loaded\n            backend = UsbTools.find_backend()\n            loader = backend.create_loader()()\n            loader.load(args.virtual)\n\n        try:\n            add_custom_devices(Ftdi, args.vidpid, force_hex=True)\n        except ValueError as exc:\n            argparser.error(str(exc))\n\n        eeprom = FtdiEeprom()\n        eeprom.open(args.device, size=args.size, model=args.eeprom)\n        if args.erase or args.full_erase:\n            eeprom.erase()\n        if args.input:\n            eeprom.load_config(args.input, args.load)\n        if args.serial_number:\n            eeprom.set_serial_number(args.serial_number)\n        if args.manufacturer:\n            eeprom.set_manufacturer_name(args.manufacturer)\n        if args.product:\n            eeprom.set_product_name(args.product)\n        for conf in args.config or []:\n            if conf in ('?', 'help'):\n                helpstr = ', '.join(sorted(eeprom.properties))\n                print(fill(helpstr, initial_indent='  ',\n                           subsequent_indent='  '))\n                sys_exit(1)\n            for sep in ':=':\n                if sep in conf:\n                    name, value = conf.split(sep, 1)\n                    if not value:\n                        argparser.error(f'Configuration {conf} without value')\n                    if value == 'help':\n                        value = '?'\n                    helpio = StringIO()\n                    eeprom.set_property(name, value, helpio)\n                    helpstr = helpio.getvalue()\n                    if helpstr:\n                        print(fill(helpstr, initial_indent='  ',\n                                   subsequent_indent='  '))\n                        sys_exit(1)\n                    break\n            else:\n                argparser.error(f'Missing name:value separator in {conf}')\n        if args.vid:\n            eeprom.set_property('vendor_id', args.vid)\n        if args.pid:\n            eeprom.set_property('product_id', args.pid)\n        if args.hexdump:\n            print(hexdump(eeprom.data))\n        if args.hexblock is not None:\n            indent = ' ' * args.hexblock\n            for pos in range(0, len(eeprom.data), 16):\n                hexa = ' '.join([f'{x:02x}' for x in eeprom.data[pos:pos+16]])\n                print(indent, hexa, sep='')\n        if args.update:\n            if eeprom.commit(False, no_crc=args.full_erase):\n                eeprom.reset_device()\n        if args.verbose > 0:\n            eeprom.dump_config()\n        if args.output:\n            if args.output == '-':\n                eeprom.save_config(stdout)\n            else:\n                with open(args.output, 'wt') as ofp:\n                    eeprom.save_config(ofp)\n\n    except (ImportError, IOError, NotImplementedError, ValueError) as exc:\n        print(f'\\nError: {exc}', file=stderr)\n        if debug:\n            print(format_exc(chain=False), file=stderr)\n        sys_exit(1)\n    except KeyboardInterrupt:\n        sys_exit(2)\n    finally:\n        eeprom.close()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/bin/ftdi_urls.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2019-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"List valid FTDI device URLs and descriptors.\"\"\"\n\nfrom argparse import ArgumentParser, FileType\nfrom logging import Formatter, StreamHandler, DEBUG, ERROR\nfrom sys import exit as sys_exit, modules, stderr\nfrom traceback import format_exc\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.misc import add_custom_devices\n\n\ndef main():\n    \"\"\"Entry point.\"\"\"\n    debug = False\n    try:\n        argparser = ArgumentParser(description=modules[__name__].__doc__)\n        argparser.add_argument('-P', '--vidpid', action='append',\n                               help='specify a custom VID:PID device ID, '\n                                    'may be repeated')\n        argparser.add_argument('-V', '--virtual', type=FileType('r'),\n                               help='use a virtual device, specified as YaML')\n        argparser.add_argument('-v', '--verbose', action='count', default=0,\n                               help='increase verbosity')\n        argparser.add_argument('-d', '--debug', action='store_true',\n                               help='enable debug mode')\n        args = argparser.parse_args()\n        debug = args.debug\n\n        loglevel = max(DEBUG, ERROR - (10 * args.verbose))\n        loglevel = min(ERROR, loglevel)\n        if debug:\n            formatter = Formatter('%(asctime)s.%(msecs)03d %(name)-20s '\n                                  '%(message)s', '%H:%M:%S')\n        else:\n            formatter = Formatter('%(message)s')\n        FtdiLogger.set_formatter(formatter)\n        FtdiLogger.set_level(loglevel)\n        FtdiLogger.log.addHandler(StreamHandler(stderr))\n\n        if args.virtual:\n            # pylint: disable=import-outside-toplevel\n            from pyftdi.usbtools import UsbTools\n            # Force PyUSB to use PyFtdi test framework for USB backends\n            UsbTools.BACKENDS = ('pyftdi.tests.backend.usbvirt', )\n            # Ensure the virtual backend can be found and is loaded\n            backend = UsbTools.find_backend()\n            loader = backend.create_loader()()\n            loader.load(args.virtual)\n\n        try:\n            add_custom_devices(Ftdi, args.vidpid, force_hex=True)\n        except ValueError as exc:\n            argparser.error(str(exc))\n\n        Ftdi.show_devices()\n\n    except (ImportError, IOError, NotImplementedError, ValueError) as exc:\n        print(f'\\nError: {exc}', file=stderr)\n        if debug:\n            print(format_exc(chain=False), file=stderr)\n        sys_exit(1)\n    except KeyboardInterrupt:\n        sys_exit(2)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/bin/i2cscan.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright (c) 2018-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"Tiny I2C bus scanner.\"\"\"\n\n# pylint: disable=broad-except\n\nfrom argparse import ArgumentParser, FileType\nfrom logging import Formatter, StreamHandler, getLogger, DEBUG, ERROR\nfrom sys import exit as sys_exit, modules, stderr\nfrom traceback import format_exc\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.i2c import I2cController, I2cNackError\nfrom pyftdi.misc import add_custom_devices\n\n\nclass I2cBusScanner:\n    \"\"\"Scan I2C bus to find slave.\n\n       Emit the I2C address message, but no data. Detect any ACK on each valid\n       address.\n    \"\"\"\n\n    SMB_READ_RANGE = list(range(0x30, 0x38)) + list(range(0x50, 0x60))\n\n    HIGHEST_I2C_SLAVE_ADDRESS = 0x78\n\n    @classmethod\n    def scan(cls, url: str, smb_mode: bool = True, force: bool = False) \\\n            -> None:\n        \"\"\"Scan an I2C bus to detect slave device.\n\n           :param url: FTDI URL\n           :param smb_mode: whether to use SMBbus restrictions or regular I2C\n                            mode.\n        \"\"\"\n        i2c = I2cController()\n        slaves = []\n        getLogger('pyftdi.i2c').setLevel(ERROR)\n        try:\n            i2c.set_retry_count(1)\n            i2c.force_clock_mode(force)\n            i2c.configure(url)\n            for addr in range(cls.HIGHEST_I2C_SLAVE_ADDRESS+1):\n                port = i2c.get_port(addr)\n                if smb_mode:\n                    try:\n                        if addr in cls.SMB_READ_RANGE:\n                            port.read(0)\n                            slaves.append('R')\n                        else:\n                            port.write([])\n                            slaves.append('W')\n                    except I2cNackError:\n                        slaves.append('.')\n                else:\n                    try:\n                        port.read(0)\n                        slaves.append('R')\n                        continue\n                    except I2cNackError:\n                        pass\n                    try:\n                        port.write([])\n                        slaves.append('W')\n                    except I2cNackError:\n                        slaves.append('.')\n        finally:\n            i2c.terminate()\n        columns = 16\n        row = 0\n        print('  ', ''.join(f' {col:01X} ' for col in range(columns)))\n        while True:\n            chunk = slaves[row:row+columns]\n            if not chunk:\n                break\n            print(f' {row//columns:01X}:', '  '.join(chunk))\n            row += columns\n\n\ndef main():\n    \"\"\"Entry point.\"\"\"\n    debug = False\n    try:\n        argparser = ArgumentParser(description=modules[__name__].__doc__)\n        argparser.add_argument('device', nargs='?', default='ftdi:///?',\n                               help='serial port device name')\n        argparser.add_argument('-S', '--no-smb', action='store_true',\n                               default=False,\n                               help='use regular I2C mode vs. SMBbus scan')\n        argparser.add_argument('-P', '--vidpid', action='append',\n                               help='specify a custom VID:PID device ID, '\n                                    'may be repeated')\n        argparser.add_argument('-V', '--virtual', type=FileType('r'),\n                               help='use a virtual device, specified as YaML')\n        argparser.add_argument('-v', '--verbose', action='count', default=0,\n                               help='increase verbosity')\n        argparser.add_argument('-d', '--debug', action='store_true',\n                               help='enable debug mode')\n        argparser.add_argument('-F', '--force', action='store_true',\n                               help='force clock mode (for FT2232D)')\n        args = argparser.parse_args()\n        debug = args.debug\n\n        if not args.device:\n            argparser.error('Serial device not specified')\n\n        loglevel = max(DEBUG, ERROR - (10 * args.verbose))\n        loglevel = min(ERROR, loglevel)\n        if debug:\n            formatter = Formatter('%(asctime)s.%(msecs)03d %(name)-20s '\n                                  '%(message)s', '%H:%M:%S')\n        else:\n            formatter = Formatter('%(message)s')\n        FtdiLogger.log.addHandler(StreamHandler(stderr))\n        FtdiLogger.set_formatter(formatter)\n        FtdiLogger.set_level(loglevel)\n\n        if args.virtual:\n            # pylint: disable=import-outside-toplevel\n            from pyftdi.usbtools import UsbTools\n            # Force PyUSB to use PyFtdi test framework for USB backends\n            UsbTools.BACKENDS = ('pyftdi.tests.backend.usbvirt', )\n            # Ensure the virtual backend can be found and is loaded\n            backend = UsbTools.find_backend()\n            loader = backend.create_loader()()\n            loader.load(args.virtual)\n\n        try:\n            add_custom_devices(Ftdi, args.vidpid, force_hex=True)\n        except ValueError as exc:\n            argparser.error(str(exc))\n\n        I2cBusScanner.scan(args.device, not args.no_smb, args.force)\n\n    except (ImportError, IOError, NotImplementedError, ValueError) as exc:\n        print(f'\\nError: {exc}', file=stderr)\n        if debug:\n            print(format_exc(chain=False), file=stderr)\n        sys_exit(1)\n    except KeyboardInterrupt:\n        sys_exit(2)\n\n\nif __name__ == '__main__':\n    try:\n        main()\n    except Exception as _exc:\n        print(str(_exc), file=stderr)\n"
  },
  {
    "path": "pyftdi/bin/pyterm.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Simple Python serial terminal\n\"\"\"\n\n# Copyright (c) 2010-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2016, Emmanuel Bouaziz <ebouaziz@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=broad-except\n# pylint: disable=wrong-import-position\n\nfrom argparse import ArgumentParser, FileType\nfrom atexit import register\nfrom collections import deque\nfrom logging import Formatter, StreamHandler, DEBUG, ERROR\nfrom os import environ, linesep, stat\nfrom re import search\nfrom sys import exit as sys_exit, modules, platform, stderr, stdout\nfrom time import sleep\nfrom threading import Event, Thread\nfrom traceback import format_exc\nfrom _thread import interrupt_main\n\n# pylint: disable=import-error\n# pylint: disable=import-outside-toplevel\n\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.misc import to_bps, add_custom_devices\nfrom pyftdi.term import Terminal\n\n\nclass MiniTerm:\n    \"\"\"A mini serial terminal to demonstrate pyserial extensions\"\"\"\n\n    DEFAULT_BAUDRATE = 115200\n\n    def __init__(self, device, baudrate=None, parity=None, rtscts=False,\n                 debug=False):\n        self._terminal = Terminal()\n        self._device = device\n        self._baudrate = baudrate or self.DEFAULT_BAUDRATE\n        self._port = self._open_port(self._device, self._baudrate, parity,\n                                     rtscts, debug)\n        self._resume = False\n        self._silent = False\n        self._rxq = deque()\n        self._rxe = Event()\n        self._debug = debug\n        register(self._cleanup)\n\n    def run(self, fullmode=False, loopback=False, silent=False,\n            localecho=False, autocr=False):\n        \"\"\"Switch to a pure serial terminal application\"\"\"\n\n        self._terminal.init(fullmode)\n        print(f'Entering minicom mode @ { self._port.baudrate} bps')\n        stdout.flush()\n        self._resume = True\n        # start the reader (target to host direction) within a dedicated thread\n        args = [loopback]\n        if self._device.startswith('ftdi://'):\n            # with pyftdi/pyusb/libusb stack, there is no kernel buffering\n            # which means that a UART source with data burst may overflow the\n            # FTDI HW buffer while the SW stack is dealing with formatting\n            # and console output. Use an intermediate thread to pop out data\n            # out from the HW as soon as it is made available, and use a deque\n            # to serve the actual reader thread\n            args.append(self._get_from_source)\n            sourcer = Thread(target=self._sourcer, daemon=True)\n            sourcer.start()\n        else:\n            # regular kernel buffered device\n            args.append(self._get_from_port)\n        reader = Thread(target=self._reader, args=tuple(args), daemon=True)\n        reader.start()\n        # start the writer (host to target direction)\n        self._writer(fullmode, silent, localecho, autocr)\n\n    def _sourcer(self):\n        try:\n            while self._resume:\n                data = self._port.read(4096)\n                if not data:\n                    continue\n                self._rxq.append(data)\n                self._rxe.set()\n        except Exception as ex:\n            self._resume = False\n            print(str(ex), file=stderr)\n            interrupt_main()\n\n    def _get_from_source(self):\n        while not self._rxq and self._resume:\n            if self._rxe.wait(0.1):\n                self._rxe.clear()\n                break\n        if not self._rxq:\n            return bytearray()\n        return self._rxq.popleft()\n\n    def _get_from_port(self):\n        try:\n            return self._port.read(4096)\n        except OSError as ex:\n            self._resume = False\n            print(str(ex), file=stderr)\n            interrupt_main()\n            return bytearray()\n        except Exception as ex:\n            print(str(ex), file=stderr)\n            return bytearray()\n\n    def _reader(self, loopback, getfunc):\n        \"\"\"Loop forever, processing received serial data in terminal mode\"\"\"\n        try:\n            # Try to read as many bytes as possible at once, and use a short\n            # timeout to avoid blocking for more data\n            self._port.timeout = 0.050\n            while self._resume:\n                if self._silent:\n                    sleep(0.25)\n                    continue\n                data = getfunc()\n                if data:\n                    stdout.write(data.decode('utf8', errors='replace'))\n                    stdout.flush()\n                if loopback:\n                    self._port.write(data)\n        except KeyboardInterrupt:\n            return\n        except Exception as exc:\n            print(f'Exception: {exc}')\n            if self._debug:\n                print(format_exc(chain=False), file=stderr)\n            interrupt_main()\n\n    def _writer(self, fullmode, silent, localecho, crlf=0):\n        \"\"\"Loop and copy console->serial until EOF character is found\"\"\"\n        while self._resume:\n            try:\n                char = self._terminal.getkey()\n                if fullmode and ord(char) == 0x2:    # Ctrl+B\n                    self._cleanup(True)\n                    return\n                if self._terminal.IS_MSWIN:\n                    if ord(char) in (0, 224):\n                        char = self._terminal.getkey()\n                        self._port.write(self._terminal.getch_to_escape(char))\n                        continue\n                    if ord(char) == 0x3:    # Ctrl+C\n                        raise KeyboardInterrupt('Ctrl-C break')\n                if silent:\n                    if ord(char) == 0x6:    # Ctrl+F\n                        self._silent = True\n                        print('Silent\\n')\n                        continue\n                    if ord(char) == 0x7:    # Ctrl+G\n                        self._silent = False\n                        print('Reg\\n')\n                        continue\n                if localecho:\n                    stdout.write(char.decode('utf8', errors='replace'))\n                    stdout.flush()\n                if crlf:\n                    if char == b'\\n':\n                        self._port.write(b'\\r')\n                        if crlf > 1:\n                            continue\n                self._port.write(char)\n            except KeyError:\n                continue\n            except KeyboardInterrupt:\n                if fullmode:\n                    if self._terminal.IS_MSWIN:\n                        self._port.write(b'\\x03')\n                    continue\n                self._cleanup(True)\n\n    def _cleanup(self, *args):\n        \"\"\"Cleanup resource before exiting\"\"\"\n        if args and args[0]:\n            print(f'{linesep}Aborting...')\n        try:\n            self._resume = False\n            if self._port:\n                # wait till the other thread completes\n                sleep(0.5)\n                try:\n                    rem = self._port.inWaiting()\n                except IOError:\n                    # maybe a bug in underlying wrapper...\n                    rem = 0\n                # consumes all the received bytes\n                for _ in range(rem):\n                    self._port.read()\n                self._port.close()\n                self._port = None\n                print('Bye.')\n        except Exception as ex:\n            print(str(ex), file=stderr)\n        finally:\n            if self._terminal:\n                self._terminal.reset()\n                self._terminal = None\n\n    @staticmethod\n    def _open_port(device, baudrate, parity, rtscts, debug=False):\n        \"\"\"Open the serial communication port\"\"\"\n        try:\n            from serial.serialutil import SerialException\n            from serial import PARITY_NONE\n        except ImportError as exc:\n            raise ImportError(\"Python serial module not installed\") from exc\n        try:\n            from serial import serial_for_url, VERSION as serialver\n            # use a simple regex rather than adding a new dependency on the\n            # more complete 'packaging' module\n            vmo = search(r'^(\\d+)\\.(\\d+)', serialver)\n            if not vmo:\n                # unable to parse version\n                raise ValueError()\n            if tuple(int(x) for x in vmo.groups()) < (3, 0):\n                # pysrial version is too old\n                raise ValueError()\n        except (ValueError, IndexError, ImportError) as exc:\n            raise ImportError(\"pyserial 3.0+ is required\") from exc\n        # the following import enables serial protocol extensions\n        if device.startswith('ftdi:'):\n            try:\n                from pyftdi import serialext\n                serialext.touch()\n            except ImportError as exc:\n                raise ImportError(\"PyFTDI module not installed\") from exc\n        try:\n            port = serial_for_url(device,\n                                  baudrate=baudrate,\n                                  parity=parity or PARITY_NONE,\n                                  rtscts=rtscts,\n                                  timeout=0)\n            if not port.is_open:\n                port.open()\n            if not port.is_open:\n                raise IOError(f\"Cannot open port '{device}'\")\n            if debug:\n                backend = port.BACKEND if hasattr(port, 'BACKEND') else '?'\n                print(f\"Using serial backend '{backend}'\")\n            return port\n        except SerialException as exc:\n            raise IOError(str(exc)) from exc\n\n\ndef get_default_device() -> str:\n    \"\"\"Return the default comm device, depending on the host/OS.\"\"\"\n    envdev = environ.get('FTDI_DEVICE', '')\n    if envdev:\n        return envdev\n    if platform == 'win32':\n        device = 'COM1'\n    elif platform == 'darwin':\n        device = '/dev/cu.usbserial'\n    elif platform == 'linux':\n        device = '/dev/ttyS0'\n    else:\n        device = ''\n    try:\n        stat(device)\n    except OSError:\n        device = 'ftdi:///1'\n    return device\n\n\ndef main():\n    \"\"\"Main routine\"\"\"\n    debug = False\n    try:\n        default_device = get_default_device()\n        argparser = ArgumentParser(description=modules[__name__].__doc__)\n        argparser.add_argument('-f', '--fullmode', dest='fullmode',\n                               action='store_true',\n                               help='use full terminal mode, exit with '\n                                    '[Ctrl]+B')\n        argparser.add_argument('device', nargs='?', default=default_device,\n                               help=f'serial port device name '\n                                    f'(default: {default_device}')\n        argparser.add_argument('-b', '--baudrate',\n                               default=str(MiniTerm.DEFAULT_BAUDRATE),\n                               help=f'serial port baudrate '\n                                    f'(default: {MiniTerm.DEFAULT_BAUDRATE})')\n        argparser.add_argument('-w', '--hwflow',\n                               action='store_true',\n                               help='hardware flow control')\n        argparser.add_argument('-e', '--localecho',\n                               action='store_true',\n                               help='local echo mode (print all typed chars)')\n        argparser.add_argument('-r', '--crlf',\n                               action='count', default=0,\n                               help='prefix LF with CR char, use twice to '\n                                    'replace all LF with CR chars')\n        argparser.add_argument('-l', '--loopback',\n                               action='store_true',\n                               help='loopback mode (send back all received '\n                                    'chars)')\n        argparser.add_argument('-s', '--silent', action='store_true',\n                               help='silent mode')\n        argparser.add_argument('-P', '--vidpid', action='append',\n                               help='specify a custom VID:PID device ID, '\n                                    'may be repeated')\n        argparser.add_argument('-V', '--virtual', type=FileType('r'),\n                               help='use a virtual device, specified as YaML')\n        argparser.add_argument('-v', '--verbose', action='count',\n                               help='increase verbosity')\n        argparser.add_argument('-d', '--debug', action='store_true',\n                               help='enable debug mode')\n        args = argparser.parse_args()\n        debug = args.debug\n\n        if not args.device:\n            argparser.error('Serial device not specified')\n\n        loglevel = max(DEBUG, ERROR - (10 * (args.verbose or 0)))\n        loglevel = min(ERROR, loglevel)\n        if debug:\n            formatter = Formatter('%(asctime)s.%(msecs)03d %(name)-20s '\n                                  '%(message)s', '%H:%M:%S')\n        else:\n            formatter = Formatter('%(message)s')\n        FtdiLogger.set_formatter(formatter)\n        FtdiLogger.set_level(loglevel)\n        FtdiLogger.log.addHandler(StreamHandler(stderr))\n\n        if args.virtual:\n            from pyftdi.usbtools import UsbTools\n            # Force PyUSB to use PyFtdi test framework for USB backends\n            UsbTools.BACKENDS = ('pyftdi.tests.backend.usbvirt', )\n            # Ensure the virtual backend can be found and is loaded\n            backend = UsbTools.find_backend()\n            loader = backend.create_loader()()\n            loader.load(args.virtual)\n\n        try:\n            add_custom_devices(Ftdi, args.vidpid, force_hex=True)\n        except ValueError as exc:\n            argparser.error(str(exc))\n\n        miniterm = MiniTerm(device=args.device,\n                            baudrate=to_bps(args.baudrate),\n                            parity='N',\n                            rtscts=args.hwflow,\n                            debug=args.debug)\n        miniterm.run(args.fullmode, args.loopback, args.silent, args.localecho,\n                     args.crlf)\n\n    except (IOError, ValueError) as exc:\n        print(f'\\nError: {exc}', file=stderr)\n        if debug:\n            print(format_exc(chain=False), file=stderr)\n        sys_exit(1)\n    except KeyboardInterrupt:\n        sys_exit(2)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/bin/uphy.sh",
    "content": "#!/bin/sh\n\n# Load/unload kernel extension helper for macOS\n\nsystem=$(uname -s)\nif [ \"${system}\" != \"Darwin\" ]; then\n    echo \"This script is dedicated to macOS\" >&2\n    exit 1\nfi\nversion=$(sw_vers -productVersion | cut -d. -f2)\nif [ ${version} -lt 9 ]; then\n    echo \"This version of OS X does not use an Apple FTDI driver\"\n    exit 0\nfi\nif [ ${version} -gt 13 ]; then\n    echo \"Apple FTDI driver on this macOS version should not be unloaded\" >&2\n    exit 1\nfi\nkextstat 2>/dev/null | grep com.apple.driver.AppleUSBFTDI > /dev/null\nif [ $? -eq 0 ]; then\n    echo \"Admin priviledge required to unload Apple FTDI driver\"\n    sudo kextunload -v -bundle com.apple.driver.AppleUSBFTDI\nelse\n   if [ \"$1\" = \"0\" ]; then\n      echo \"Admin priviledge required to load Apple FTDI driver\"\n      sudo kextload -v -bundle com.apple.driver.AppleUSBFTDI\n   fi\nfi\n"
  },
  {
    "path": "pyftdi/bits.py",
    "content": "# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2008-2016, Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"Bit field and sequence management.\"\"\"\n\nfrom typing import Iterable, List, Optional, Tuple, Union\nfrom .misc import is_iterable, xor\n\n# pylint: disable=invalid-name\n# pylint: disable=unneeded-not\n# pylint: disable=duplicate-key\n\n\nclass BitSequenceError(Exception):\n    \"\"\"Bit sequence error\"\"\"\n\n\nclass BitSequence:\n    \"\"\"Bit sequence.\n\n       Support most of the common bit operations: or, and, shift, comparison,\n       and conversion from and to integral values.\n\n       Bit sequence objects are iterable.\n\n       Can be initialized with another bit sequence, a integral value,\n       a sequence of bytes or an iterable of common boolean values.\n\n       :param value:  initial value\n       :param msb:    most significant bit first or not\n       :param length: count of signficant bits in the bit sequence\n       :param bytes_: initial value specified as a sequence of bytes\n       :param msby:   most significant byte first or not\n    \"\"\"\n\n    def __init__(self, value: Union['BitSequence', str, int] = None,\n                 msb: bool = False, length: int = 0,\n                 bytes_: Optional[bytes] = None, msby: bool = True):\n        \"\"\"Instantiate a new bit sequence.\n        \"\"\"\n        self._seq = bytearray()\n        seq = self._seq\n        if value and bytes_:\n            raise BitSequenceError(\"Cannot inialize with both a value and \"\n                                   \"bytes\")\n        if bytes_:\n            provider = list(bytes_).__iter__() if msby else reversed(bytes_)\n            for byte in provider:\n                if isinstance(byte, str):\n                    byte = ord(byte)\n                elif byte > 0xff:\n                    raise BitSequenceError(\"Invalid byte value\")\n                b = []\n                for _ in range(8):\n                    b.append(bool(byte & 0x1))\n                    byte >>= 1\n                if msb:\n                    b.reverse()\n                seq.extend(b)\n        else:\n            value = self._tomutable(value)\n        if isinstance(value, int):\n            self._init_from_integer(value, msb, length)\n        elif isinstance(value, BitSequence):\n            self._init_from_sibling(value, msb)\n        elif is_iterable(value):\n            self._init_from_iterable(value, msb)\n        elif value is None:\n            pass\n        else:\n            raise BitSequenceError(f\"Cannot initialize from '{type(value)}'\")\n        self._update_length(length, msb)\n\n    def sequence(self) -> bytearray:\n        \"\"\"Return the internal representation as a new mutable sequence\"\"\"\n        return bytearray(self._seq)\n\n    def reverse(self) -> 'BitSequence':\n        \"\"\"In-place reverse\"\"\"\n        self._seq.reverse()\n        return self\n\n    def invert(self) -> 'BitSequence':\n        \"\"\"In-place invert sequence values\"\"\"\n        self._seq = bytearray([x ^ 1 for x in self._seq])\n        return self\n\n    def append(self, seq) -> 'BitSequence':\n        \"\"\"Concatenate a new BitSequence\"\"\"\n        if not isinstance(seq, BitSequence):\n            seq = BitSequence(seq)\n        self._seq.extend(seq.sequence())\n        return self\n\n    def lsr(self, count: int) -> None:\n        \"\"\"Left shift rotate\"\"\"\n        count %= len(self)\n        self._seq[:] = self._seq[count:] + self._seq[:count]\n\n    def rsr(self, count: int) -> None:\n        \"\"\"Right shift rotate\"\"\"\n        count %= len(self)\n        self._seq[:] = self._seq[-count:] + self._seq[:-count]\n\n    def tobit(self) -> bool:\n        \"\"\"Degenerate the sequence into a single bit, if possible\"\"\"\n        if len(self) != 1:\n            raise BitSequenceError(\"BitSequence should be a scalar\")\n        return bool(self._seq[0])\n\n    def tobyte(self, msb: bool = False) -> int:\n        \"\"\"Convert the sequence into a single byte value, if possible\"\"\"\n        if len(self) > 8:\n            raise BitSequenceError(\"Cannot fit into a single byte\")\n        byte = 0\n        pos = -1 if not msb else 0\n        # copy the sequence\n        seq = self._seq[:]\n        while seq:\n            byte <<= 1\n            byte |= seq.pop(pos)\n        return byte\n\n    def tobytes(self, msb: bool = False, msby: bool = False) -> bytearray:\n        \"\"\"Convert the sequence into a sequence of byte values\"\"\"\n        blength = (len(self)+7) & (~0x7)\n        sequence = list(self._seq)\n        if not msb:\n            sequence.reverse()\n        bytes_ = bytearray()\n        for pos in range(0, blength, 8):\n            seq = sequence[pos:pos+8]\n            byte = 0\n            while seq:\n                byte <<= 1\n                byte |= seq.pop(0)\n            bytes_.append(byte)\n        if msby:\n            bytes_.reverse()\n        return bytes_\n\n    @staticmethod\n    def _tomutable(value: Union[str, Tuple]) -> List:\n        \"\"\"Convert a immutable sequence into a mutable one\"\"\"\n        if isinstance(value, tuple):\n            # convert immutable sequence into a list so it can be popped out\n            value = list(value)\n        elif isinstance(value, str):\n            # convert immutable sequence into a list so it can be popped out\n            if value.startswith('0b'):\n                value = list(value[2:])\n            else:\n                value = list(value)\n        return value\n\n    def _init_from_integer(self, value: int, msb: bool, length: int) -> None:\n        \"\"\"Initialize from any integer value\"\"\"\n        bl = length or -1\n        seq = self._seq\n        while bl:\n            seq.append(bool(value & 1))\n            value >>= 1\n            if not value:\n                break\n            bl -= 1\n        if msb:\n            seq.reverse()\n\n    def _init_from_iterable(self, iterable: Iterable, msb: bool) -> None:\n        \"\"\"Initialize from an iterable\"\"\"\n        smap = {'0': 0, '1': 1, False: 0, True: 1, 0: 0, 1: 1}\n        seq = self._seq\n        try:\n            if msb:\n                seq.extend([smap[bit] for bit in reversed(iterable)])\n            else:\n                seq.extend([smap[bit] for bit in iterable])\n        except KeyError as exc:\n            raise BitSequenceError('Invalid binary character in initializer') \\\n                    from exc\n\n    def _init_from_sibling(self, value: 'BitSequence', msb: bool) -> None:\n        \"\"\"Initialize from a fellow object\"\"\"\n        self._seq = value.sequence()\n        if msb:\n            self._seq.reverse()\n\n    def _update_length(self, length, msb):\n        \"\"\"If a specific length is specified, extend the sequence as\n           expected\"\"\"\n        if length and (len(self) < length):\n            extra = bytearray([False] * (length-len(self)))\n            if msb:\n                extra.extend(self._seq)\n                self._seq = extra\n            else:\n                self._seq.extend(extra)\n\n    def __iter__(self):\n        return self._seq.__iter__()\n\n    def __reversed__(self):\n        return self._seq.__reversed__()\n\n    def __getitem__(self, index):\n        if isinstance(index, slice):\n            return self.__class__(value=self._seq[index])\n        return self._seq[index]\n\n    def __setitem__(self, index, value):\n        if isinstance(value, BitSequence):\n            if issubclass(value.__class__, self.__class__) and \\\n               value.__class__ != self.__class__:\n                raise BitSequenceError(\"Cannot set item with instance of a \"\n                                       \"subclass\")\n        if isinstance(index, slice):\n            value = self.__class__(value, length=len(self._seq[index]))\n            self._seq[index] = value.sequence()\n        else:\n            if not isinstance(value, BitSequence):\n                value = self.__class__(value)\n            val = value.tobit()\n            if index > len(self._seq):\n                raise BitSequenceError(\"Cannot change the sequence size\")\n            self._seq[index] = val\n\n    def __len__(self):\n        return len(self._seq)\n\n    def __eq__(self, other):\n        return self._cmp(other) == 0\n\n    def __ne__(self, other):\n        return not self == other\n\n    def __le__(self, other):\n        return not self._cmp(other) <= 0\n\n    def __lt__(self, other):\n        return not self._cmp(other) < 0\n\n    def __ge__(self, other):\n        return not self._cmp(other) >= 0\n\n    def __gt__(self, other):\n        return not self._cmp(other) > 0\n\n    def _cmp(self, other):\n        # the bit sequence should be of the same length\n        ld = len(self) - len(other)\n        if ld:\n            return ld\n        for n, (x, y) in enumerate(zip(self._seq, other.sequence()), start=1):\n            if xor(x, y):\n                return n\n        return 0\n\n    def __repr__(self):\n        # cannot use bin() as it truncates the MSB zero bits\n        return ''.join([b and '1' or '0' for b in reversed(self._seq)])\n\n    def __str__(self):\n        chunks = []\n        srepr = repr(self)\n        length = len(self)\n        for i in range(0, length, 8):\n            if i:\n                j = -i\n            else:\n                j = None\n            chunks.append(srepr[-i-8:j])\n        return f'{len(self)}: {\" \".join(reversed(chunks))}'\n\n    def __int__(self):\n        value = 0\n        for b in reversed(self._seq):\n            value <<= 1\n            value |= b and 1\n        return value\n\n    def __and__(self, other):\n        if not isinstance(other, self.__class__):\n            raise BitSequenceError('Need a BitSequence to combine')\n        if len(self) != len(other):\n            raise BitSequenceError('Sequences must be the same size')\n        return self.__class__(value=list(map(lambda x, y: x and y,\n                                             self._seq, other.sequence())))\n\n    def __or__(self, other):\n        if not isinstance(other, self.__class__):\n            raise BitSequenceError('Need a BitSequence to combine')\n        if len(self) != len(other):\n            raise BitSequenceError('Sequences must be the same size')\n        return self.__class__(value=list(map(lambda x, y: x or y,\n                                             self._seq, other.sequence())))\n\n    def __add__(self, other):\n        return self.__class__(value=self._seq + other.sequence())\n\n    def __ilshift__(self, count):\n        count %= len(self)\n        seq = bytearray([0]*count)\n        seq.extend(self._seq[:-count])\n        self._seq = seq\n        return self\n\n    def __irshift__(self, count):\n        count %= len(self)\n        seq = self._seq[count:]\n        seq.extend([0]*count)\n        self._seq = seq\n        return self\n\n    def inc(self) -> None:\n        \"\"\"Increment the sequence\"\"\"\n        for p, b in enumerate(self._seq):\n            b ^= True\n            self._seq[p] = b\n            if b:\n                break\n\n    def dec(self) -> None:\n        \"\"\"Decrement the sequence\"\"\"\n        for p, b in enumerate(self._seq):\n            b ^= True\n            self._seq[p] = b\n            if not b:\n                break\n\n    def invariant(self) -> bool:\n        \"\"\"Tells whether all bits of the sequence are of the same value.\n\n           Return the value, or ValueError if the bits are not of the same\n           value\n        \"\"\"\n        try:\n            ref = self._seq[0]\n        except IndexError as exc:\n            raise ValueError('Empty sequence') from exc\n        if len(self._seq) == 1:\n            return ref\n        for b in self._seq[1:]:\n            if b != ref:\n                raise ValueError('Bits do no match')\n        return ref\n\n\nclass BitZSequence(BitSequence):\n    \"\"\"Tri-state bit sequence manipulation.\n\n       Support most of the BitSequence operations, with an extra high-Z state\n\n       :param value:  initial value\n       :param msb:    most significant bit first or not\n       :param length: count of signficant bits in the bit sequence\n    \"\"\"\n\n    __slots__ = ['_seq']\n\n    Z = 0xff  # maximum byte value\n\n    def __init__(self, value=None, msb=False, length=0):\n        BitSequence.__init__(self, value=value, msb=msb, length=length)\n\n    def invert(self):\n        self._seq = [x in (None, BitZSequence.Z) and BitZSequence.Z or x ^ 1\n                     for x in self._seq]\n        return self\n\n    def tobyte(self, msb=False):\n        raise BitSequenceError(f'Type {type(self)} cannot be converted to '\n                               f'byte')\n\n    def tobytes(self, msb=False, msby=False):\n        raise BitSequenceError(f'Type {type(self)} cannot be converted to '\n                               f'bytes')\n\n    def matches(self, other):\n        # pylint: disable=missing-function-docstring\n        if not isinstance(self, BitSequence):\n            raise BitSequenceError('Not a BitSequence instance')\n        # the bit sequence should be of the same length\n        ld = len(self) - len(other)\n        if ld:\n            return ld\n        for (x, y) in zip(self._seq, other.sequence()):\n            if BitZSequence.Z in (x, y):\n                continue\n            if x is not y:\n                return False\n        return True\n\n    def _init_from_iterable(self, iterable, msb):\n        \"\"\"Initialize from an iterable\"\"\"\n        smap = {'0': 0, '1': 1, 'Z': BitZSequence.Z,\n                False: 0, True: 1, None: BitZSequence.Z,\n                0: 0, 1: 1, BitZSequence.Z: BitZSequence.Z}\n        seq = self._seq\n        try:\n            if msb:\n                seq.extend([smap[bit] for bit in reversed(iterable)])\n            else:\n                seq.extend([smap[bit] for bit in iterable])\n        except KeyError as exc:\n            raise BitSequenceError(\"Invalid binary character in initializer\") \\\n                    from exc\n\n    def __repr__(self):\n        smap = {False: '0', True: '1', BitZSequence.Z: 'Z'}\n        return ''.join([smap[b] for b in reversed(self._seq)])\n\n    def __int__(self):\n        if BitZSequence.Z in self._seq:\n            raise BitSequenceError(\"High-Z BitSequence cannot be converted to \"\n                                   \"an integral type\")\n        return BitSequence.__int__(self)\n\n    def __cmp__(self, other):\n        # the bit sequence should be of the same length\n        ld = len(self) - len(other)\n        if ld:\n            return ld\n        for n, (x, y) in enumerate(zip(self._seq, other.sequence()), start=1):\n            if x is not y:\n                return n\n        return 0\n\n    def __and__(self, other):\n        if not isinstance(self, BitSequence):\n            raise BitSequenceError('Need a BitSequence-compliant object to '\n                                   'combine')\n        if len(self) != len(other):\n            raise BitSequenceError('Sequences must be the same size')\n\n        def andz(x, y):\n            \"\"\"Compute the boolean AND operation for a tri-state boolean\"\"\"\n            if BitZSequence.Z in (x, y):\n                return BitZSequence.Z\n            return x and y\n        return self.__class__(\n            value=list(map(andz, self._seq, other.sequence())))\n\n    def __or__(self, other):\n        if not isinstance(self, BitSequence):\n            raise BitSequenceError('Need a BitSequence-compliant object to '\n                                   'combine')\n        if len(self) != len(other):\n            raise BitSequenceError('Sequences must be the same size')\n\n        def orz(x, y):\n            \"\"\"Compute the boolean OR operation for a tri-state boolean\"\"\"\n            if BitZSequence.Z in (x, y):\n                return BitZSequence.Z\n            return x or y\n        return self.__class__(value=list(map(orz, self._seq,\n                                             other.sequence())))\n\n    def __rand__(self, other):\n        return self.__and__(other)\n\n    def __ror__(self, other):\n        return self.__or__(other)\n\n    def __radd__(self, other):\n        return self.__class__(value=other) + self\n\n\nclass BitField:\n    \"\"\"Bit field class to access and modify an integral value\n\n       Beware the slices does not behave as regular Python slices:\n       bitfield[3:5] means b3..b5, NOT b3..b4 as with regular slices\n    \"\"\"\n\n    __slots__ = ['_val']\n\n    def __init__(self, value=0):\n        self._val = value\n\n    def to_seq(self, msb=0, lsb=0):\n        \"\"\"Return the BitFiled as a sequence of boolean value\"\"\"\n        seq = bytearray()\n        count = 0\n        value = self._val\n        while value:\n            count += 1\n            value >>= 1\n        for x in range(lsb, max(msb, count)):\n            seq.append(bool((self._val >> x) & 1))\n        return tuple(reversed(seq))\n\n    def __getitem__(self, index):\n        if isinstance(index, slice):\n            if index.stop == index.start:\n                return None\n            if index.stop < index.start:\n                offset = index.stop\n                count = index.start-index.stop+1\n            else:\n                offset = index.start\n                count = index.stop-index.start+1\n            mask = (1 << count)-1\n            return (self._val >> offset) & mask\n        return (self._val >> index) & 1\n\n    def __setitem__(self, index, value):\n        if isinstance(index, slice):\n            if index.stop == index.start:\n                return\n            if index.stop < index.start:\n                offset = index.stop\n                count = index.start-index.stop+1\n            else:\n                offset = index.start\n                count = index.stop-index.start+1\n            mask = (1 << count)-1\n            value = (value & mask) << offset\n            mask <<= offset\n            self._val = (self._val & ~mask) | value\n        else:\n            if isinstance(value, bool):\n                value = int(value)\n            value = (value & int(1)) << index\n            mask = int(1) << index\n            self._val = (self._val & ~mask) | value\n\n    def __int__(self):\n        return self._val\n\n    def __str__(self):\n        return bin(self._val)\n"
  },
  {
    "path": "pyftdi/doc/api/eeprom.rst",
    "content": "\n.. include:: ../defs.rst\n\n:mod:`eeprom` - EEPROM API\n--------------------------\n\n.. module :: pyftdi.eeprom\n\n\nQuickstart\n~~~~~~~~~~\n\nExample: dump the EEPROM content\n\n.. code-block:: python\n\n    # Instantiate an EEPROM manager\n    eeprom = FtdiEeprom()\n\n    # Select the FTDI device to access (the interface is mandatory but any\n    # valid interface for the device fits)\n    eeprom.open('ftdi://ftdi:2232h/1')\n\n    # Show the EEPROM content\n    eeprom.dump_config()\n\n    # Show the raw EEPROM content\n    from pyftdi.misc import hexdump\n    print(hexdump(eeprom.data))\n\n\nExample: update the serial number\n\n.. code-block:: python\n\n    # Instantiate an EEPROM manager\n    eeprom = FtdiEeprom()\n\n    # Select the FTDI device to access\n    eeprom.open('ftdi://ftdi:2232h/1')\n\n    # Change the serial number\n    eeprom.set_serial_number('123456')\n\n    # Commit the change to the EEPROM\n    eeprom.commit(dry_run=False)\n\n\nClasses\n~~~~~~~\n\n.. autoclass :: FtdiEeprom\n :members:\n\n\nExceptions\n~~~~~~~~~~\n\n.. autoexception :: FtdiEepromError\n\n\nTests\n~~~~~\n\n.. code-block:: shell\n\n   # optional: specify an alternative FTDI device\n   export FTDI_DEVICE=ftdi://ftdi:2232h/1\n   PYTHONPATH=. python3 pyftdi/tests/eeprom.py\n"
  },
  {
    "path": "pyftdi/doc/api/ftdi.rst",
    "content": ".. -*- coding: utf-8 -*-\n\n.. include:: ../defs.rst\n\n:mod:`ftdi` - FTDI low-level driver\n-----------------------------------\n\n.. module :: pyftdi.ftdi\n\nThis module implements access to the low level FTDI hardware. There are very\nfew reasons to use this module directly. Most of PyFtdi_ features are available\nthrough the dedicated :doc:`APIs <index>`.\n\nClasses\n~~~~~~~\n\n.. autoclass :: Ftdi\n :members:\n\n\nExceptions\n~~~~~~~~~~\n\n.. autoexception :: FtdiError\n.. autoexception :: FtdiMpsseError\n.. autoexception :: FtdiFeatureError\n"
  },
  {
    "path": "pyftdi/doc/api/gpio.rst",
    "content": ".. -*- coding: utf-8 -*-\n\n.. include:: ../defs.rst\n\n:mod:`gpio` - GPIO API\n----------------------\n\n.. module :: pyftdi.gpio\n\nDirect drive GPIO pins of FTDI device.\n\n.. note::\n\n  This mode is mutually exclusive with advanced serial MPSSE features, such as\n  |I2C|, SPI, JTAG, ...\n\n  If you need to use GPIO pins and MPSSE interface on the same port, you need\n  to use the dedicated API. This shared mode is supported with the\n  :doc:`SPI API <spi>` and the :doc:`I2C API <i2c>`.\n\n.. warning::\n\n  This API does not provide access to the special CBUS port of FT232R, FT232H,\n  FT230X and FT231X devices. See :ref:`cbus_gpio` for details.\n\nQuickstart\n~~~~~~~~~~\n\nSee ``tests/gpio.py`` example\n\n\nClasses\n~~~~~~~\n\n.. autoclass :: GpioPort\n\n.. autoclass :: GpioAsyncController\n :members:\n\n.. autoclass :: GpioSyncController\n :members:\n\n.. autoclass :: GpioMpsseController\n :members:\n\n\nExceptions\n~~~~~~~~~~\n\n.. autoexception :: GpioException\n\n\nInfo about GPIO API\n~~~~~~~~~~~~~~~~~~~\n\nSee :doc:`../gpio` for details\n"
  },
  {
    "path": "pyftdi/doc/api/i2c.rst",
    "content": "\n.. include:: ../defs.rst\n\n:mod:`i2c` - |I2C| API\n----------------------\n\n.. module :: pyftdi.i2c\n\n\nQuickstart\n~~~~~~~~~~\n\nExample: communication with an |I2C| GPIO expander\n\n.. code-block:: python\n\n    # Instantiate an I2C controller\n    i2c = I2cController()\n\n    # Configure the first interface (IF/1) of the FTDI device as an I2C master\n    i2c.configure('ftdi://ftdi:2232h/1')\n\n    # Get a port to an I2C slave device\n    slave = i2c.get_port(0x21)\n\n    # Send one byte, then receive one byte\n    slave.exchange([0x04], 1)\n\n    # Write a register to the I2C slave\n    slave.write_to(0x06, b'\\x00')\n\n    # Read a register from the I2C slave\n    slave.read_from(0x00, 1)\n\nExample: mastering the |I2C| bus with a complex transaction\n\n.. code-block:: python\n\n   from time import sleep\n\n   port = I2cController().get_port(0x56)\n\n   # emit a START sequence is read address, but read no data and keep the bus\n   # busy\n   port.read(0, relax=False)\n\n   # wait for ~1ms\n   sleep(0.001)\n\n   # write 4 bytes, without neither emitting the start or stop sequence\n   port.write(b'\\x00\\x01', relax=False, start=False)\n\n   # read 4 bytes, without emitting the start sequence, and release the bus\n   port.read(4, start=False)\n\nSee also pyi2cflash_ module and ``tests/i2c.py``, which provide more detailed\nexamples on how to use the |I2C| API.\n\n\nClasses\n~~~~~~~\n\n.. autoclass :: I2cPort\n :members:\n\n.. autoclass :: I2cGpioPort\n :members:\n\n.. autoclass :: I2cController\n :members:\n\n\nExceptions\n~~~~~~~~~~\n\n.. autoexception :: I2cIOError\n.. autoexception :: I2cNackError\n.. autoexception:: I2cTimeoutError\n\n\nGPIOs\n~~~~~\n\nSee :doc:`../gpio` for details\n\nTests\n~~~~~\n\n|I2C| sample tests expect:\n  * TCA9555 device on slave address 0x21\n  * ADXL345 device on slave address 0x53\n\nCheckout a fresh copy from PyFtdi_ github repository.\n\nSee :doc:`../pinout` for FTDI wiring.\n\n.. code-block:: shell\n\n   # optional: specify an alternative FTDI device\n   export FTDI_DEVICE=ftdi://ftdi:2232h/1\n   # optional: increase log level\n   export FTDI_LOGLEVEL=DEBUG\n   # be sure to connect the appropriate I2C slaves to the FTDI I2C bus and run\n   PYTHONPATH=. python3 pyftdi/tests/i2c.py\n\n\n.. _i2c_limitations:\n\nCaveats\n~~~~~~~\n\nOpen-collector bus\n``````````````````\n\n|I2C| uses only two bidirectional open collector (or open drain) lines, pulled\nup with resistors. These resistors are also required on an |I2C| bus when an\nFTDI master is used.\n\nHowever, most FTDI devices do not use open collector outputs. Some software\ntricks are used to fake open collector mode when possible, for example to\nsample for slave ACK/NACK, but most communication (R/W, addressing, data)\ncannot use open collector mode. This means that most FTDI devices source\ncurrent to the SCL and SDA lines. FTDI HW is able to cope with conflicting\nsignalling, where FTDI HW forces a line the high logical level while a slave\nforces it to the low logical level, and limits the sourced current. You may\nwant to check your schematics if the slave is not able to handle 4 .. 16 mA\ninput current in SCL and SDA, for example. The maximal source current depends\non the FTDI device and the attached EEPROM configuration which may be used to\nlimit further down the sourced current.\n\nFortunately, FT232H device is fitted with real open collector outputs, and\nPyFtdi always enable this mode on SCL and SDA lines when a FT232H device is\nused.\n\nOther FTDI devices such as FT2232H, FT4232H and FT4232HA do not support open\ncollector mode, and source current to SCL and SDA lines.\n\nClock streching\n```````````````\n\nClock stretching is supported through a hack that re-uses the JTAG adaptative\nclock mode designed for ARM devices. FTDI HW drives SCL on ``AD0`` (`BD0`), and\nsamples the SCL line on : the 8\\ :sup:`th` pin of a port ``AD7`` (``BD7``).\n\nWhen a FTDI device without an open collector capability is used\n(FT2232H, FT4232H, FT4232HA) the current sourced from AD0 may prevent proper\nsampling ofthe SCL line when the slave attempts to strech the clock. It is\ntherefore recommended to add a low forward voltage drop diode to `AD0` to\nprevent AD0 to source current to the SCL bus. See the wiring section.\n\nSpeed\n`````\n\nDue to the FTDI MPSSE engine limitations, the actual bitrate for write\noperations over I2C is very slow. As the I2C protocol enforces that each I2C\nexchanged byte needs to be acknowledged by the peer, a I2C byte cannot be\nwritten to the slave before the previous byte has been acknowledged by the\nslave and read back by the I2C master, that is the host. This requires several\nUSB transfer for each byte, on top of each latency of the USB stack may add up.\nWith the introduction of PyFtdi_ v0.51, read operations have been optimized so\nthat long read operations are now much faster thanwith previous PyFtdi_\nversions, and exhibits far shorter latencies.\n\nUse of PyFtdi_ should nevetherless carefully studied and is not recommended if\nyou need to achieve medium to high speed write operations with a slave\n(relative to the I2C clock...). Dedicated I2C master such as FT4222H device is\nlikely a better option, but is not currently supported with PyFtdi_ as it uses\na different communication protocol.\n\n.. _i2c_wiring:\n\nWiring\n~~~~~~\n\n.. figure:: ../images/i2c_wiring.png\n   :scale: 50 %\n   :alt: I2C wiring\n   :align: right\n\n   Fig.1: FT2232H with clock stretching\n\n* ``AD0`` should be connected to the SCL bus\n* ``AD1`` and ``AD2`` should be both connected to the SDA bus\n* ``AD7`` should be connected to the SCL bus, if clock streching is required\n* remaining pins can be freely used as regular GPIOs.\n\n*Fig.1*:\n\n* ``D1`` is only required when clock streching is used along with\n  FT2232H, FT4232H or FT4232HA devices. It should not be fit with an FT232H.\n* ``AD7`` may be used as a regular GPIO with clock stretching is not required.\n"
  },
  {
    "path": "pyftdi/doc/api/index.rst",
    "content": "API documentation\n=================\n\n.. include:: ../defs.rst\n\n|release|\n---------\n\n.. toctree::\n   :maxdepth: 1\n   :glob:\n\n   ftdi\n   gpio\n   i2c\n   spi\n   uart\n   usbtools\n   misc\n   eeprom\n"
  },
  {
    "path": "pyftdi/doc/api/misc.rst",
    "content": ".. -*- coding: utf-8 -*-\n\n:mod:`misc` - Miscellaneous helpers\n-----------------------------------\n\nFunctions\n~~~~~~~~~\n\n.. automodule:: pyftdi.misc\n   :members:\n\n"
  },
  {
    "path": "pyftdi/doc/api/spi.rst",
    "content": ".. include:: ../defs.rst\n\n:mod:`spi` - SPI API\n--------------------\n\n.. module :: pyftdi.spi\n\nQuickstart\n~~~~~~~~~~\n\nExample: communication with a SPI data flash (half-duplex example)\n\n.. code-block:: python\n\n    # Instantiate a SPI controller\n    spi = SpiController()\n\n    # Configure the first interface (IF/1) of the FTDI device as a SPI master\n    spi.configure('ftdi://ftdi:2232h/1')\n\n    # Get a port to a SPI slave w/ /CS on A*BUS3 and SPI mode 0 @ 12MHz\n    slave = spi.get_port(cs=0, freq=12E6, mode=0)\n\n    # Request the JEDEC ID from the SPI slave\n    jedec_id = slave.exchange([0x9f], 3)\n\n\nExample: communication with a remote SPI device using full-duplex mode\n\n.. code-block:: python\n\n    # Instantiate a SPI controller\n    # We need want to use A*BUS4 for /CS, so at least 2 /CS lines should be\n    # reserved for SPI, the remaining IO are available as GPIOs.\n    spi = SpiController(cs_count=2)\n\n    # Configure the first interface (IF/1) of the FTDI device as a SPI master\n    spi.configure('ftdi://ftdi:2232h/1')\n\n    # Get a port to a SPI slave w/ /CS on A*BUS4 and SPI mode 2 @ 10MHz\n    slave = spi.get_port(cs=1, freq=10E6, mode=2)\n\n    # Synchronous exchange with the remote SPI slave\n    write_buf = b'\\x01\\x02\\x03'\n    read_buf = slave.exchange(write_buf, duplex=True)\n\nExample: communication with a SPI device and an extra GPIO\n\n.. code-block:: python\n\n    # Instantiate a SPI controller\n    spi = SpiController()\n\n    # Configure the first interface (IF/1) of the first FTDI device as a\n    # SPI master\n    spi.configure('ftdi://::/1')\n\n    # Get a SPI port to a SPI slave w/ /CS on A*BUS3 and SPI mode 0 @ 12MHz\n    slave = spi.get_port(cs=0, freq=12E6, mode=0)\n\n    # Get GPIO port to manage extra pins, use A*BUS4 as GPO, A*BUS5 as GPI\n    gpio = spi.get_gpio()\n    gpio.set_direction(pins=0b0011_0000, direction=0b0001_0000)\n\n    # Assert GPO pin\n    gpio.write(0x10)\n    # Write to SPI slace\n    slave.write(b'hello world!')\n    # Release GPO pin\n    gpio.write(0x00)\n    # Test GPI pin\n    pin = bool(gpio.read() & 0x20)\n\n\nExample: managing non-byte aligned transfers\n\n.. code-block:: python\n\n    # Instantiate a SPI controller\n    spi = SpiController()\n\n    # Configure the first interface (IF/1) of the first FTDI device as a\n    # SPI master\n    spi.configure('ftdi://::/1')\n\n    # Get a SPI port to a SPI slave w/ /CS on A*BUS3\n    slave = spi.get_port(cs=0)\n\n    # write 6 first bits of a byte buffer\n    slave.write(b'\\xff', droptail=2)\n\n    # read only 13 bits from a slave (13 clock cycles)\n    # only the 5 MSBs of the last byte are valid, 3 LSBs are force to zero\n    slave.read(2, droptail=3)\n\nSee also pyspiflash_ module and ``tests/spi.py``, which provide more detailed\nexamples on how to use the SPI API.\n\n\nClasses\n~~~~~~~\n\n.. autoclass :: SpiPort\n :members:\n\n.. autoclass :: SpiGpioPort\n :members:\n\n.. autoclass :: SpiController\n :members:\n\nExceptions\n~~~~~~~~~~\n\n.. autoexception :: SpiIOError\n\n\nGPIOs\n~~~~~\n\nSee :doc:`../gpio` for details\n\nTests\n~~~~~\n\nSPI sample tests expect:\n  * MX25L1606E device on /CS 0, SPI mode 0\n  * ADXL345 device on /CS 1, SPI mode 2\n  * RFDA2125 device on /CS 2, SPI mode 0\n\nCheckout a fresh copy from PyFtdi_ github repository.\n\nSee :doc:`../pinout` for FTDI wiring.\n\n.. code-block:: shell\n\n   # optional: specify an alternative FTDI device\n   export FTDI_DEVICE=ftdi://ftdi:2232h/1\n   # optional: increase log level\n   export FTDI_LOGLEVEL=DEBUG\n   # be sure to connect the appropriate SPI slaves to the FTDI SPI bus and run\n   PYTHONPATH=. python3 pyftdi/tests/spi.py\n\n.. _spi_limitations:\n\nLimitations\n~~~~~~~~~~~\n\nSPI Modes 1 & 3\n```````````````\n\nFTDI hardware does not support cpha=1 (mode 1 and mode 3). As stated in\nApplication Node 114:\n\n   \"*It is recommended that designers review the SPI Slave\n   data sheet to determine the SPI mode implementation. FTDI device can only\n   support mode 0 and mode 2 due to the limitation of MPSSE engine.*\".\n\nSupport for mode 1 and mode 3 is implemented with some workarounds, but\ngenerated signals may not be reliable: YMMV. It is only available with -H\nseries (232H, 2232H, 4232H, 4232HA).\n\nThe 3-clock phase mode which has initially be designed to cope with |I2C|\nsignalling is used to delay the data lines from the clock signals. A direct\nconsequence of this workaround is that SCLK duty cycle is not longer 50% but\n25% (mode 1) or 75% (mode 3). Again, support for mode 1 and mode 3 should be\nconsidered as a kludge, you've been warned.\n\nTime-sensitive usage\n````````````````````\n\nDue to the MPSSE engine limitation, it is not possible to achieve\ntime-controlled request sequence. In other words, if the SPI slave needs to\nreceive command sequences at precise instants - for example ADC or DAC\ndevices - PyFtdi_ use is not recommended. This limitation is likely to apply\nto any library that relies on FTDI device. The USB bus latency and the lack\nof timestamped commands always add jitter and delays, with no easy known\nworkaround.\n\n.. _spi_wiring:\n\nWiring\n~~~~~~\n\n.. figure:: ../images/spi_wiring.png\n   :scale: 50 %\n   :alt: SPI wiring\n   :align: right\n\n   Fig.1: FT2232H with two SPI slaves\n\n* ``AD0`` should be connected to SCLK\n* ``AD1`` should be connected to MOSI\n* ``AD2`` should be connected to MISO\n* ``AD3`` should be connected to the first slave /CS.\n* ``AD4`` should be connected to the second slave /CS, if any\n* remaining pins can be freely used as regular GPIOs.\n\n*Fig.1*:\n\n* ``AD4`` may be used as a regular GPIO if a single SPI slave is used\n* ``AD5`` may be used as another /CS signal for a third slave, in this case\n  the first available GPIO is ``AD6``, etc.\n"
  },
  {
    "path": "pyftdi/doc/api/uart.rst",
    "content": ".. include:: ../defs.rst\n\n:mod:`serialext` - UART API\n---------------------------\n\nThere is no dedicated module for the UART API, as PyFtdi_ acts as a backend of\nthe well-known pyserial_ module.\n\nThe pyserial_ backend module is implemented as the `serialext.protocol_ftdi`\nmodule. It is not documented here as no direct call to this module is required,\nas the UART client should use the regular pyserial_ API.\n\nUsage\n~~~~~\n\nTo enable PyFtdi_ as a pyserial_ backend, use the following import:\n\n.. code-block:: python\n\n    import pyftdi.serialext\n\nThen use\n\n.. code-block:: python\n\n    pyftdi.serialext.serial_for_url(url, **options)\n\nto open a pyserial_ serial port instance.\n\n\nQuickstart\n~~~~~~~~~~\n\n.. code-block:: python\n\n    # Enable pyserial extensions\n    import pyftdi.serialext\n\n    # Open a serial port on the second FTDI device interface (IF/2) @ 3Mbaud\n    port = pyftdi.serialext.serial_for_url('ftdi://ftdi:2232h/2', baudrate=3000000)\n\n    # Send bytes\n    port.write(b'Hello World')\n\n    # Receive bytes\n    data = port.read(1024)\n\n.. _uart_gpio:\n\nGPIO access\n~~~~~~~~~~~\n\nUART mode, the primary function of FTDI \\*232\\* devices, is somewhat limited\nwhen it comes to GPIO management, as opposed to alternative mode such as |I2C|,\nSPI and JTAG. It is not possible to assign the unused pins of an UART mode to\narbitrary GPIO functions.\n\nAll the 8 lower pins of an UART port are dedicated to the UART function,\nalthough most of them are seldomely used, as dedicated to manage a modem or a\nlegacy DCE_ device. Upper pins (b\\ :sub:`7`\\ ..b\\ :sub:`15`\\ ), on devices that\nhave ones, cannot be driven while UART port is enabled.\n\nIt is nevertheless possible to have limited access to the lower pins as GPIO,\nwith many limitations:\n\n- the GPIO direction of each pin is hardcoded and cannot be changed\n- GPIO pins cannot be addressed atomically: it is possible to read the state\n  of an input GPIO, or to change the state of an output GPIO, one after\n  another. This means than obtaining the state of several input GPIOs or\n  changing the state of several output GPIO at once is not possible.\n- some pins cannot be used as GPIO is hardware flow control is enabled.\n  Keep in mind However that HW flow control with FTDI is not reliable, see the\n  :ref:`hardware_flow_control` section.\n\nAccessing those GPIO pins is done through the UART extended pins, using their\nUART assigned name, as PySerial port attributes. See the table below:\n\n+---------------+------+-----------+-------------------------------+\n| Bit           | UART | Direction | API                           |\n+===============+======+===========+===============================+\n| b\\ :sub:`0`\\  | TX   | Out       | ``port.write(buffer)``        |\n+---------------+------+-----------+-------------------------------+\n| b\\ :sub:`1`\\  | RX   | In        | ``buffer = port.read(count)`` |\n+---------------+------+-----------+-------------------------------+\n| b\\ :sub:`2`\\  | RTS  | Out       | ``port.rts = state``          |\n+---------------+------+-----------+-------------------------------+\n| b\\ :sub:`3`\\  | CTS  | In        | ``state = port.cts``          |\n+---------------+------+-----------+-------------------------------+\n| b\\ :sub:`4`\\  | DTR  | Out       | ``port.dtr = state``          |\n+---------------+------+-----------+-------------------------------+\n| b\\ :sub:`5`\\  | DSR  | In        | ``state = port.dsr``          |\n+---------------+------+-----------+-------------------------------+\n| b\\ :sub:`6`\\  | DCD  | In        | ``state = port.dcd``          |\n+---------------+------+-----------+-------------------------------+\n| b\\ :sub:`7`\\  | RI   | In        | ``state = port.ri``           |\n+---------------+------+-----------+-------------------------------+\n\nCBUS support\n````````````\n\nSome FTDI devices (FT232R, FT232H, FT230X, FT231X) support additional CBUS\npins, which can be used as regular GPIOs pins. See :ref:`CBUS GPIO<cbus_gpio>`\nfor details.\n\n\n.. _pyterm:\n\nMini serial terminal\n~~~~~~~~~~~~~~~~~~~~\n\n``pyterm.py`` is a simple serial terminal that can be used to test the serial\nport feature. See the :ref:`tools` chapter to locate this tool.\n\n::\n\n  Usage: pyterm.py [-h] [-f] [-b BAUDRATE] [-w] [-e] [-r] [-l] [-s] [-P VIDPID]\n                   [-V VIRTUAL] [-v] [-d]\n                   [device]\n\n  Simple Python serial terminal\n\n  positional arguments:\n    device                serial port device name (default: ftdi:///1)\n\n  optional arguments:\n    -h, --help            show this help message and exit\n    -f, --fullmode        use full terminal mode, exit with [Ctrl]+B\n    -b BAUDRATE, --baudrate BAUDRATE\n                          serial port baudrate (default: 115200)\n    -w, --hwflow          hardware flow control\n    -e, --localecho       local echo mode (print all typed chars)\n    -r, --crlf            prefix LF with CR char, use twice to replace all LF\n                          with CR chars\n    -l, --loopback        loopback mode (send back all received chars)\n    -s, --silent          silent mode\n    -P VIDPID, --vidpid VIDPID\n                          specify a custom VID:PID device ID, may be repeated\n    -V VIRTUAL, --virtual VIRTUAL\n                          use a virtual device, specified as YaML\n    -v, --verbose         increase verbosity\n    -d, --debug           enable debug mode\n\nIf the PyFtdi module is not yet installed and ``pyterm.py`` is run from the\narchive directory, ``PYTHONPATH`` should be defined to the current directory::\n\n    PYTHONPATH=$PWD pyftdi/bin/pyterm.py ftdi:///?\n\nThe above command lists all the available FTDI device ports. To avoid conflicts\nwith some shells such as `zsh`, escape the `?` char as ``ftdi:///\\?``.\n\nTo start up a serial terminal session, specify the FTDI port to use, for\nexample:\n\n.. code-block:: shell\n\n    # detect all FTDI connected devices\n    PYTHONPATH=. python3 pyftdi/bin/ftdi_urls.py\n    # use the first interface of the first FT2232H as a serial port\n    PYTHONPATH=$PWD pyftdi/bin/pyterm.py ftdi://ftdi:2232/1\n\n\n.. _uart-limitations:\n\nLimitations\n~~~~~~~~~~~\n\nAlthough the FTDI H series are in theory capable of 12 MBps baudrate, baudrates\nabove 6 Mbps are barely usable.\n\nSee the following table for details.\n\n+------------+-------------+------------+-------------+------------+--------+\n|  Requ. bps |HW capability| 9-bit time |  Real bps   | Duty cycle | Stable |\n+============+=============+============+=============+============+========+\n| 115.2 Kbps |  115.2 Kbps |   78.08 µs | 115.26 Kbps |     49.9%  |  Yes   |\n+------------+-------------+------------+-------------+------------+--------+\n| 460.8 Kbps | 461.54 Kbps |   19.49 µs | 461.77 Kbps |     49.9%  |  Yes   |\n+------------+-------------+------------+-------------+------------+--------+\n|     1 Mbps |      1 Mbps |   8.98 µs  |  1.002 Mbps |     49.5%  |  Yes   |\n+------------+-------------+------------+-------------+------------+--------+\n|     4 Mbps |      4 Mbps |   2.24 µs  |  4.018 Mbps |       48%  |  Yes   |\n+------------+-------------+------------+-------------+------------+--------+\n|     5 Mbps |  5.052 Mbps |   1.78 µs  |  5.056 Mbps |       50%  |  Yes   |\n+------------+-------------+------------+-------------+------------+--------+\n|     6 Mbps |      6 Mbps |   1.49 µs  |  6.040 Mbps |     48.5%  |  Yes   |\n+------------+-------------+------------+-------------+------------+--------+\n|     7 Mbps |  6.857 Mbps |   1.11 µs  |  8.108 Mbps |       44%  |   No   |\n+------------+-------------+------------+-------------+------------+--------+\n|     8 Mbps |      8 Mbps |   1.11 µs  |  8.108 Mbps |   44%-48%  |   No   |\n+------------+-------------+------------+-------------+------------+--------+\n|   8.8 Mbps |  8.727 Mbps |   1.13 µs  |  7.964 Mbps |       44%  |   No   |\n+------------+-------------+------------+-------------+------------+--------+\n|   9.6 Mbps |    9.6 Mbps |   1.12 µs  |  8.036 Mbps |       48%  |   No   |\n+------------+-------------+------------+-------------+------------+--------+\n|  10.5 Mbps | 10.667 Mbps |   1.11 µs  |  8.108 Mbps |       44%  |   No   |\n+------------+-------------+------------+-------------+------------+--------+\n|    12 Mbps |     12 Mbps |   0.75 µs  |     12 Mbps |       43%  |  Yes   |\n+------------+-------------+------------+-------------+------------+--------+\n\n * 9-bit time is the measured time @ FTDI output pins for a 8-bit character\n   (start bit + 8 bit data)\n * Duty cycle is the ratio between a low-bit duration and a high-bit duration,\n   a good UART should exhibit the same duration for low bits and high bits,\n   *i.e.* a duty cycle close to 50%.\n * Stability reports whether subsequent runs, with the very same HW settings,\n   produce the same timings.\n\nAchieving a reliable connection over 6 Mbps has proven difficult, if not\nimpossible: Any baudrate greater than 6 Mbps (except the upper 12 Mbps limit)\nresults into an actual baudrate of about 8 Mbps, and suffer from clock\nfluterring [7.95 .. 8.1Mbps].\n\n.. _hardware_flow_control:\n\nHardware flow control\n`````````````````````\n\nMoreover, as the hardware flow control of the FTDI device is not a true HW\nflow control. Quoting FTDI application note:\n\n   *If CTS# is logic 1 it is indicating the external device cannot accept more\n   data. the FTxxx will stop transmitting within 0~3 characters, depending on\n   what is in the buffer.*\n   **This potential 3 character overrun does occasionally present problems.**\n   *Customers shoud be made aware the FTxxx is a USB device and not a \"normal\"\n   RS232 device as seen on a PC. As such the device operates on a packet\n   basis as opposed to a byte basis.*\n\n"
  },
  {
    "path": "pyftdi/doc/api/usbtools.rst",
    "content": ".. -*- coding: utf-8 -*-\n\n:mod:`usbtools` - USB tools\n---------------------------\n\n.. module :: pyftdi.usbtools\n\n\nClasses\n~~~~~~~\n\n.. autoclass :: UsbTools\n :members:\n\n\nExceptions\n~~~~~~~~~~\n\n.. autoexception :: UsbToolsError\n"
  },
  {
    "path": "pyftdi/doc/authors.rst",
    "content": "Authors\n-------\n\nMain developers\n~~~~~~~~~~~~~~~\n\n * Emmanuel Blot <emmanuel.blot@free.fr>\n * Emmanuel Bouaziz <ebouaziz@free.fr>\n\nContributors\n~~~~~~~~~~~~\n\n * Nikus-V\n * Dave McCoy\n * Adam Feuer\n * endlesscoil\n * humm (Fabien Benureau)\n * dlharmon\n * DavidWC\n * Sebastian\n * Anders (anders-code)\n * Andrea Concil\n * Darren Garnier\n * Michael Leonard\n * nopeppermint (Stefan)\n * hannesweisbach\n * Vianney le Clément de Saint-Marcq\n * Pete Schwamb\n * Will Richey\n * sgoadhouse\n * tavip (Octavian Purdila)\n * Tim Legrand\n * vestom\n * meierphil\n * etherfi\n * sgoadhouse\n * jnmacd\n * naushir\n * markmelvin (Mark Melvin)\n * stiebrs\n * mpratt14\n * alexforencich\n * TedKus\n * Amanita-muscaria\n * len0rd\n * Rod Whitby\n * Kornel Swierzy\n * Taisuke Yamada\n * Michael Niewöhner\n * Kalofin\n * Henry Au-Yeung\n * Roman Dobrodii\n * Mark Mentovai\n * Alessandro Zini\n * Sjoerd Simons\n * David Schneider\n"
  },
  {
    "path": "pyftdi/doc/conf.py",
    "content": "# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: skip-file\n\nimport os\nimport re\nimport sys\n\n# pip3 install wheel\n# pip3 install sphinx-autodoc-typehints sphinx-pypi-upload sphinx_rtd_theme\n# python3 setup.py build_sphinx\n# sphinx-build -b html ../pyftdi/pyftdi/doc .\n\ntopdir = os.path.abspath(os.path.join(os.path.dirname(__file__),\n                                      os.pardir, os.pardir))\nsys.path.append(topdir)\n\n\ndef read(where, *parts):\n    \"\"\"\n    Build an absolute path from *parts* and and return the contents of the\n    resulting file.  Assume UTF-8 encoding.\n    \"\"\"\n    with open(os.path.join(where, *parts), 'rt') as f:\n        return f.read()\n\n\ndef find_meta(meta):\n    \"\"\"\n    Extract __*meta*__ from meta_file.\n    \"\"\"\n    meta_match = re.search(\n        r\"^__{meta}__ = ['\\\"]([^'\\\"]*)['\\\"]\".format(meta=meta),\n        meta_file, re.M\n    )\n    if meta_match:\n        return meta_match.group(1)\n    raise RuntimeError(\"Unable to find __{meta}__ string.\".format(meta=meta))\n\n\nmeta_file = read(topdir, 'pyftdi', '__init__.py')\n\nversion = find_meta('version')\n\nneeds_sphinx = '2.1'\nextensions = ['sphinx.ext.autodoc',\n              'sphinx.ext.doctest',\n              'sphinx_autodoc_typehints']\ntemplates_path = ['templates']\nsource_suffix = '.rst'\nmaster_doc = 'index'\nproject = find_meta('title')\ncontact = '%s <%s>' % (find_meta('author'), find_meta('email'))\ncopyright = '2010-2025, %s' % contact\nshow_authors = True\n\nhtml_theme = 'sphinx_rtd_theme'\nhtmlhelp_basename = 'doc'\n\npreamble = r'''\n\\usepackage{wallpaper}\n\\usepackage{titlesec}\n\n\\titleformat{\\chapter}[display]{}{\\filleft\\scshape\\chaptername\\enspace\\thechapter}{-2pt}{\\filright \\Huge \\bfseries}[\\vskip4.5pt\\titlerule]\n\\titleformat{name=\\chapter, numberless}[block]{}{}{0pt}{\\filright \\Huge \\bfseries}[\\vskip4.5pt\\titlerule]\n\n\\titlespacing{\\chapter}{0pt}{0pt}{1cm}\n'''\n\nlatex_elements = {\n  'papersize': 'a4paper',\n  'fncychap': '',  # No Title Page\n  'releasename': '',\n  'sphinxsetup': 'hmargin={2.0cm,2.0cm}, vmargin={2.5cm,2.5cm}, marginpar=5cm',\n  'classoptions': ',openany,oneside',  # Avoid blank page aftre TOC, etc.\n  'preamble': preamble,\n  'releasename': ''\n}\n\nlatex_documents = [\n  ('index', '%s.tex' % project.lower(),\n   '%s Documentation' % project,\n   contact, u'manual'),\n]\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\nlatex_toplevel_sectioning = \"chapter\"\n\nman_pages = [\n  ('index', project,\n   '%s Documentation' % project,\n   [contact], 1)\n]\n\ntexinfo_documents = [\n  ('index', project,\n   '%s Documentation' % project,\n   contact, '',\n   '%s Documentation' % project,\n   'Miscellaneous'),\n]\n\n\ndef setup(app):\n    app.add_css_file('https://fonts.googleapis.com/css?family=Raleway')\n"
  },
  {
    "path": "pyftdi/doc/defs.rst",
    "content": ".. |I2C| replace:: I\\ :sup:`2`\\ C\n\n.. _FT232R: https://www.ftdichip.com/Products/ICs/FT232R.htm\n.. _FT230X: https://www.ftdichip.com/Products/ICs/FT230X.html\n.. _FT2232D: https://www.ftdichip.com/Products/ICs/FT2232D.htm\n.. _FT232H: https://www.ftdichip.com/Products/ICs/FT232H.htm\n.. _FT2232H: https://www.ftdichip.com/Products/ICs/FT2232H.html\n.. _FT4232H: https://www.ftdichip.com/Products/ICs/FT4232H.htm\n.. _FT4232HA: http://ftdichip.com/products/ft4232haq/\n.. _FTDI_Recovery: https://www.ftdichip.com/Support/Documents/AppNotes/AN_136%20Hi%20Speed%20Mini%20Module%20EEPROM%20Disaster%20Recovery.pdf\n.. _PyFtdi: https://www.github.com/eblot/pyftdi\n.. _PyFtdiTools: https://github.com/eblot/pyftdi/tree/master/pyftdi/bin\n.. _PyJtagTools: https://www.github.com/eblot/pyjtagtools\n.. _FTDI: https://www.ftdichip.com/\n.. _PyUSB: https://pyusb.github.io/pyusb/\n.. _Python: https://www.python.org/\n.. _pyserial: https://pythonhosted.org/pyserial/\n.. _libftdi: https://www.intra2net.com/en/developer/libftdi/\n.. _pyspiflash: https://github.com/eblot/pyspiflash/\n.. _pyi2cflash: https://github.com/eblot/pyi2cflash/\n.. _libusb: https://www.libusb.info/\n.. _Libusb on Windows: https://github.com/libusb/libusb/wiki/Windows\n.. _Libusb win32: https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/\n.. _Zadig: https://zadig.akeo.ie/\n.. _FTDI macOS guide: https://www.ftdichip.com/Support/Documents/AppNotes/AN_134_FTDI_Drivers_Installation_Guide_for_MAC_OSX.pdf\n.. _Libusb issue on macOs: https://github.com/libusb/libusb/commit/5e45e0741daee4fa295c6cc977edfb986c872152\n.. _FT_PROG: https://www.ftdichip.com/Support/Utilities.htm#FT_PROG\n.. _fstring: https://www.python.org/dev/peps/pep-0498\n.. _DCE: https://en.wikipedia.org/wiki/Data_circuit-terminating_equipment\n.. _PEP_498: https://www.python.org/dev/peps/pep-0498\n.. _PEP_526: https://www.python.org/dev/peps/pep-0526\n.. _ruamel.yaml: https://pypi.org/project/ruamel.yaml\n.. _PyFtdiWin: https://github.com/mariusgreuel/pyftdiwin\n\n.. Restructured Text levels\n\n..   Level 1\n..   -------\n\n..   Level 2\n..   ~~~~~~~\n\n..   Level 3\n..   ```````\n\n..   Level 4\n..   .......\n\n..   Level 5\n..   +++++++\n"
  },
  {
    "path": "pyftdi/doc/eeprom.rst",
    "content": ".. include:: defs.rst\n\nEEPROM management\n-----------------\n\n.. warning::\n   Writing to the EEPROM can cause very **undesired** effects if the wrong\n   value is written in the wrong place. You can even essentially **brick** your\n   FTDI device. Use this function only with **extreme** caution.\n\n   It is not recommended to use this application with devices that use an\n   internal EEPROM such as FT232R or FT-X series, as if something goes wrong,\n   recovery options are indeed limited. FT232R internal EEPROM seems to be\n   unstable, even the official FT_PROG_ tool from FTDI may fail to fix it on\n   some conditions.\n\n   If using a Hi-Speed Mini Module and you brick for FTDI device, see\n   FTDI_Recovery_\n\n\nSupported features\n~~~~~~~~~~~~~~~~~~\n\nEEPROM support is under active development.\n\nSome features may be wrongly decoded, as each FTDI model implements a different\nfeature map, and more test/validation are required.\n\nThe :doc:`EEPROM API <api/eeprom>` implements the upper API to access the\nEEPROM content.\n\n.. _ftconf:\n\nEEPROM configuration tool\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``ftconf.py`` is a companion script to help managing the content of the FTDI\nEEPROM from the command line. See the :ref:`tools` chapter to locate this tool.\n\n::\n\n  usage: ftconf.py [-h] [-i INPUT] [-l {all,raw,values}] [-o OUTPUT] [-V VIRTUAL]\n                   [-P VIDPID] [-M EEPROM] [-S {128,256,1024}] [-x] [-X HEXBLOCK]\n                   [-s SERIAL_NUMBER] [-m MANUFACTURER] [-p PRODUCT] [-c CONFIG]\n                   [--vid VID] [--pid PID] [-e] [-E] [-u] [-v] [-d]\n                   [device]\n\n  Simple FTDI EEPROM configurator.\n\n  positional arguments:\n    device                serial port device name\n\n  optional arguments:\n    -h, --help            show this help message and exit\n\n  Files:\n    -i INPUT, --input INPUT\n                          input ini file to load EEPROM content\n    -l {all,raw,values}, --load {all,raw,values}\n                          section(s) to load from input file\n    -o OUTPUT, --output OUTPUT\n                          output ini file to save EEPROM content\n    -V VIRTUAL, --virtual VIRTUAL\n                          use a virtual device, specified as YaML\n\n  Device:\n    -P VIDPID, --vidpid VIDPID\n                          specify a custom VID:PID device ID (search for FTDI devices)\n    -M EEPROM, --eeprom EEPROM\n                          force an EEPROM model\n    -S {128,256,1024}, --size {128,256,1024}\n                          force an EEPROM size\n\n  Format:\n    -x, --hexdump         dump EEPROM content as ASCII\n    -X HEXBLOCK, --hexblock HEXBLOCK\n                          dump EEPROM as indented hexa blocks\n\n  Configuration:\n    -s SERIAL_NUMBER, --serial-number SERIAL_NUMBER\n                          set serial number\n    -m MANUFACTURER, --manufacturer MANUFACTURER\n                          set manufacturer name\n    -p PRODUCT, --product PRODUCT\n                          set product name\n    -c CONFIG, --config CONFIG\n                          change/configure a property as key=value pair\n    --vid VID             shortcut to configure the USB vendor ID\n    --pid PID             shortcut to configure the USB product ID\n\n  Action:\n    -e, --erase           erase the whole EEPROM content\n    -E, --full-erase      erase the whole EEPROM content, including the CRC\n    -u, --update          perform actual update, use w/ care\n\n  Extras:\n    -v, --verbose         increase verbosity\n    -d, --debug           enable debug mode\n\n**Again, please read the** :doc:`license` **terms before using the EEPROM API\nor this script. You may brick your device if something goes wrong, and there\nmay be no way to recover your device.**\n\nNote that to protect the EEPROM content of unexpected modification, it is\nmandatory to specify the :ref:`-u <option_u>` flag along any alteration/change\nof the EEPROM content. Without this flag, the script performs a dry-run\nexecution of the changes, *i.e.* all actions but the write request to the\nEEPROM are executed.\n\nOnce updated, you need to unplug/plug back the device to use the new EEPROM\nconfiguration.\n\nIt is recommended to first save the current content of the EEPROM, using the\n:ref:`-o <option_o>` flag, to have a working copy of the EEPROM data before any\nattempt to modify it. It can help restoring the EEPROM if something gets wrong\nduring a subsequence update, thanks to the :ref:`-i <option_i>` option switch.\n\nMost FTDI device can run without an EEPROM. If something goes wrong, try to\nerase the EEPROM content, then restore the original content.\n\n\nOption switches\n```````````````\nIn addition to the :ref:`common_option_switches` for  PyFtdi_ tools,\n``ftconf.py`` support the following arguments:\n\n.. _option_c:\n\n``-c name=value``\n  Change a configuration in the EEPROM. This flag can be repeated as many times\n  as required to change several configuration parameter at once. Note that\n  without option ``-u``, the EEPROM content is not actually modified, the\n  script runs in dry-run mode.\n\n  The name should be separated from the value with an equal ``=`` sign or\n  alternatively a full column ``:`` character.\n\n  * To obtain the list of supported name, use the `?` wildcard: ``-c ?``, or\n    `-c help` to avoid conflicts with some shells\n  * To obtain the list of supported values for a name, use the `?` or the `help`\n    wildcard:\n    ``-c name=help``, where *name* is a supported name.\n\n  See :ref:`cbus_func` table for the alternate function associated with each\n  name.\n\n.. _option_E_:\n\n``-E``\n  Erase the full EEPROM content including the CRC. As the CRC no longer\n  validates the EEPROM content, the EEPROM configuration is ignored on the next\n  power cycle of the device, so the default FTDI configuration is used.\n\n  This may be useful to recover from a corrupted EEPROM, as when no EEPROM or a\n  blank EEPROM is detected, the FTDI falls back to a default configuration.\n\n  Note that without option :ref:`-u <option_u>`, the EEPROM content is not\n  actually modified, the script runs in dry-run mode.\n\n.. _option_e:\n\n``-e``\n  Erase the whole EEPROM and regenerates a valid CRC.\n\n  Beware that as `-e` option generates a valid CRC for the erased EEPROM\n  content, the FTDI device may identified itself as VID:PID FFFF:FFFF on next\n  reboot. You should likely use the `--vid` and `--pid` option to define a\n  valid FDTI device USB identifier with this option to ensure the device\n  identifies itself as a FTDI device on next power cycle.\n\n  Note that without option :ref:`-u <option_u>`, the EEPROM content is not\n  actually modified, the script runs in dry-run mode.\n\n  Alternatively, use `-E` option that erase the full EEPROM content including\n  the CRC.\n\n.. _option_i:\n\n``-i``\n  Load a INI file (as generated with the :ref:`-o <option_o>` option switch. It\n  is possible to select which section(s) from the INI file are loaded, using\n  :ref:`-l <option_l>` option switch. The ``values`` section may be modified,\n  as it takes precedence over the ``raw`` section. Note that without option\n  :ref:`-u <option_u>`, the EEPROM content is not actually modified, the script\n  runs in dry-run mode.\n\n.. _option_l:\n\n``-l <all|raw|values>``\n  Define which section(s) of the INI file are used to update the EEPROM content\n  along with the :ref:`-i <option_i>` option switch. Defaults to ``all``.\n\n  The supported feature set of the ``values`` is the same as the one exposed\n  through the :ref:`-c <option_c>` option switch. Unsupported feature are\n  ignored, and a warning is emitted for each unsupported feature.\n\n.. _option_M_:\n\n``-M <model>``\n  Specify the EEPROM model (93c46, 93c56, 93c66) that is connected to the FTDI\n  device. There is no reason to use this option except for recovery purposes,\n  see option `-E`. It is mutually exclusive with the `-S` option.\n\n.. _option_m:\n\n``-m <manufacturer>``\n  Assign a new manufacturer name to the device. Note that without option\n  :ref:`-u <option_u>`, the EEPROM content is not actually modified, the script\n  runs in dry-run mode. Manufacturer names with ``/`` or ``:`` characters are\n  rejected, to avoid parsing issues with FTDI :ref:`URLs <url_scheme>`.\n\n.. _option_o:\n\n``-o <output>``\n  Generate and write to the specified file the EEPROM content as decoded\n  values and a hexa dump. The special ``-`` file can be used as the output file\n  to print to the standard output. The output file contains two sections:\n\n  * ``[values]`` that contain the decoded EEPROM configuration as key, value\n    pair. Note that the keys and values can be used as configuration input, see\n    option :ref:`-c <option_c>`.\n  * ``[raw]`` that contains a compact representation of the EEPROM raw content,\n    encoded as hexadecimal strings.\n\n.. _option_p:\n\n``-p <product>``\n  Assign a new product name to the device. Note that without option :ref:`-u\n  <option_u>`, the EEPROM content is not actually modified, the script runs in\n  dry-run mode. Product names with ``/`` or ``:`` characters are rejected, to\n  avoid parsing issues with FTDI :ref:`URLs <url_scheme>`.\n\n.. _option_pid:\n\n``--pid``\n  Define the USB product identifier - as an hexadecimal number. This is a\n  shortcut for `-c product_id`\n\n.. _option_S_:\n\n``-S <size>``\n  Specify the EEPROM size -in bytes- that is connected to the FTDI device.\n  There is no reason to use this option except for recovery purposes,\n  see option `-E`. It is mutually exclusive with the `-M` option.\n\n.. _option_s:\n\n``-s <serial>``\n  Assign a new serial number to the device. Note that without option :ref:`-u\n  <option_u>`, the EEPROM content is not actually modified, the script runs in\n  dry-run mode. Serial number with ``/`` or ``:`` characters are rejected, to\n  avoid parsing issues with FTDI :ref:`URLs <url_scheme>`.\n\n.. _option_u:\n\n``-u``\n  Update the EEPROM with the new settings. Without this flag, the script runs\n  in dry-run mode, so no change is made to the EEPROM. Whenever this flag is\n  used, the EEPROM is actually updated and its checksum regenerated. If\n  something goes wrong at this point, you may brick you board, you've been\n  warned. PyFtdi_ offers neither guarantee whatsoever than altering the EEPROM\n  content is safe, nor that it is possible to recover from a bricked device.\n\n.. _option_vid:\n\n``--vid``\n  Define the USB vendor identifier - as an hexadecimal number. This is a\n  shortcut for `-c vendor_id`.\n\n.. _option_x:\n\n``-x``\n  Generate and print a hexadecimal raw dump of the EEPROM content, similar to\n  the output of the `hexdump -Cv` tool.\n\n\n.. _cbus_func:\n\nCBUS function\n`````````````\n\nThe following table describes the CBUS pin alternate functions. Note that\ndepending on the actual device, some alternate function may not be available.\n\n+-----------------+--------+--------------------------------------------------------------------------------+\n| Name            | Active | Description                                                                    |\n+=================+========+================================================================================+\n| ``TRISTATE``    | Hi-Z   | IO Pad is tri-stated                                                           |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``TXLED``       | Low    | TX activity, can be used as status for LED                                     |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``RXLED``       | Low    | RX activity, can be used as status for LED                                     |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``TXRXLED``     | Low    | TX & RX activity, can be used as status for LED                                |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``PWREN``       | Low    | USB configured, USB suspend: high                                              |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``SLEEP``       | Low    | USB suspend, typically used to power down external devices.                    |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``DRIVE0``      | Low    | Drive a constant (FT232H and FT-X only)                                        |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``DRIVE1``      | High   | Drive a constant (FT232H and FT-X only)                                        |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``GPIO``        |        | IO port for CBUS bit bang mode                                                 |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``TXDEN``       | High   | Enable transmit for RS485 mode                                                 |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``CLK48``       |        | Output 48 MHz clock (FT232R only)                                              |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``CLK30``       |        | Output 30 MHz clock (FT232H only)                                              |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``CLK24``       |        | Output 24 MHz clock (FT232R and FT-X only)                                     |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``CLK15``       |        | Output 12 MHz clock (FT232H only)                                              |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``CLK12``       |        | Output 12 MHz clock (FT232R and FT-X only)                                     |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``CLK7_5``      |        | Output 7.5 MHz clock (FT232H only)                                             |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``CLK6``        |        | Output 6 MHz clock (FT232R and FT-X only)                                      |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``BAT_DETECT``  | High   | Battery Charger Detect (FT-X only)                                             |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``BAT_NDETECT`` | Low    | Inverse signal of BAT_DETECT (FT-X only)                                       |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``I2C_TXE``     | Low    | Transmit buffer empty (FT-X only)                                              |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``I2C_RXF``     | Low    | Receive buffer full  (FT-X only)                                               |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``VBUS_SENSE``  | High   | Detect when VBUS is present via the appropriate AC IO pad (FT-X only)          |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``BB_WR``       | Low    | Synchronous Bit Bang Write strobe (FT232R and FT-X only)                       |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``BB_RD``       | Low    | Synchronous Bit Bang Read strobe (FT232R and FT-X only)                        |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``TIMESTAMP``   |        | Toggle signal each time a USB SOF is received (FT-X only)                      |\n+-----------------+--------+--------------------------------------------------------------------------------+\n| ``AWAKE``       | Low    | Do not suspend when unplugged/disconnect/suspsend (FT-X only)                  |\n+-----------------+--------+--------------------------------------------------------------------------------+\n\n\nExamples\n````````\n\n* Change product name and serial number\n\n  ::\n\n    pyftdi/bin/ftconf.py ftdi:///1 -p UartBridge -s abcd1234 -u\n\n* List supported configuration parameters\n\n  ::\n\n    pyftdi/bin/ftconf.py ftdi:///1 -c ?\n      cbus_func_0, cbus_func_1, cbus_func_2, cbus_func_3, cbus_func_4,\n      cbus_func_5, cbus_func_6, cbus_func_7, cbus_func_8, cbus_func_9,\n      channel_a_driver, channel_a_type, chip, clock_polarity,\n      flow_control, group_0_drive, group_0_schmitt, group_0_slew,\n      group_1_drive, group_1_schmitt, group_1_slew, has_serial,\n      has_usb_version, in_isochronous, lsb_data, out_isochronous,\n      power_max, powersave, product_id, remote_wakeup, self_powered,\n      suspend_pull_down, type, usb_version, vendor_id\n\n* List supported configuration values for CBUS0\n\n  ::\n\n    pyftdi/bin/ftconf.py ftdi:///1 -c cbus_func_0:?\n      AWAKE, BAT_DETECT, BAT_NDETECT, BB_RD, BB_WR, CLK12, CLK24, CLK6,\n      DRIVE0, DRIVE1, I2C_RXF, I2C_TXE, GPIO, PWREN, RXLED, SLEEP,\n      TIME_STAMP, TRISTATE, TXDEN, TXLED, TXRXLED, VBUS_SENSE\n\n* Erase the whole EEPROM including its CRC.\n\n  Once power cycle, the device should run as if no EEPROM was connected.\n  Do not use this with internal, embedded EEPROMs such as FT230X.\n\n  ::\n\n    pyftdi/bin/ftconf.py -P ffff:ffff ftdi://ffff:ffff/1 -E -u\n\n* Recover from an erased EEPROM with a valid CRC\n\n  ::\n\n    # for a FT4232 device\n    # note that ffff matches an erased EEPROM, other corrupted values may\n    # exist, such device can be identified with system tools such as lsusb\n\n    pyftdi/bin/ftconf.py -P ffff:ffff ftdi://ffff:ffff/1 -e -u \\\n        --vid 0403 --pid 6011\n\n.. _eeprom_cbus:\n\n* Configure CBUS: 0 and 3 as GPIOs, then show the device configuration\n\n  ::\n\n   pyftdi/bin/ftconf.py ftdi:///1 -v\n      -c cbus_func_0:GPIO -c cbus_func_3:GPIO\n"
  },
  {
    "path": "pyftdi/doc/features.rst",
    "content": ".. include:: defs.rst\n\nFeatures\n--------\n\nDevices\n~~~~~~~\n\n* All FTDI device ports (UART, MPSSE) can be used simultaneously.\n\n  * SPI and |I2C| SPI support simultaneous GPIO R/W access for all pins that\n    are not used for SPI/|I2C| feature.\n\n* Several FTDI adapters can be accessed simultaneously from the same Python\n  runtime instance.\n\nSupported features\n~~~~~~~~~~~~~~~~~~\n\nUART\n````\n\nSerial port, up to 6 Mbps. PyFtdi_ includes a pyserial_ emulation layer that\noffers transparent access to the FTDI serial ports through a pyserial_-\ncompliant API. The ``serialext`` directory contains a minimal serial terminal\ndemonstrating the use of this extension, and a dispatcher automatically\nselecting the serial backend (pyserial_, PyFtdi_), based on the serial port\nname.\n\nSee also :ref:`uart-limitations`.\n\nSPI master\n``````````\n\nSupported devices:\n\n=====  ===== ====== ============================================================\nMode   CPol   CPha  Status\n=====  ===== ====== ============================================================\n  0      0      0   Supported on all MPSSE devices\n  1      0      1   Workaround available for on -H series\n  2      1      0   Supported on -H series (FT232H_/FT2232H_/FT4232H_/FT4232HA_)\n  3      1      1   Workaround available for on -H series\n=====  ===== ====== ============================================================\n\nPyFtdi_ can be used with pyspiflash_ module that demonstrates how to\nuse the FTDI SPI master with a pure-Python serial flash device driver for\nseveral common devices.\n\nBoth Half-duplex (write or read) and full-duplex (synchronous write and read)\ncommunication modes are supported.\n\nExperimental support for non-byte aligned access, where up to 7 trailing bits\ncan be discarded: no clock pulse is generated for those bits, so that SPI\ntransfer of non byte-sized can be performed.\n\nSee :ref:`spi_wiring` and :ref:`spi_limitations`.\n\nNote: FTDI*232* devices cannot be used as an SPI slave.\n\n|I2C| master\n````````````\n\nSupported devices: FT232H_, FT2232H_, FT4232H_, FT4232HA_\n\nFor now, only 7-bit addresses are supported.\n\nGPIOs can be used while |I2C| mode is enabled.\n\nThe ``i2cscan.py`` script helps to discover which I2C devices are connected to\nthe FTDI I2C bus. See the :ref:`tools` chapter to locate this tool.\n\nThe pyi2cflash_ module demonstrates how to use the FTDI |I2C| master to access\nserial EEPROMS.\n\nSee :ref:`i2c_wiring` and :ref:`i2c_limitations`.\n\nNote: FTDI*232* devices cannot be used as an |I2C| slave.\n\nJTAG\n````\n\nJTAG API is limited to low-level access. It is not intented to be used for\nany flashing or debugging purpose, but may be used as a base to perform SoC\ntests and boundary scans.\n\nIt requires the PyJtagTools_ Python module which integrates a JTAG engine, while\nPyFtdi_ implements the FTDI JTAG backend.\n\nEEPROM\n``````\n\nThe ``pyftdi/bin/ftconf.py`` script helps to manage the content of the FTDI\ncompanion EEPROM.\n\n\nStatus\n~~~~~~\n\nThis project is still in beta development stage. PyFtdi_ is developed as an\nopen-source solution.\n"
  },
  {
    "path": "pyftdi/doc/gpio.rst",
    "content": ".. include:: defs.rst\n\nGPIOs\n-----\n\nOverview\n~~~~~~~~\n\nMany PyFtdi APIs give direct access to the IO pins of the FTDI devices:\n\n  * *GpioController*, implemented as ``GpioAsyncController``,\n    ``GpioSyncController`` and ``GpioMpsseController`` (see :doc:`api/gpio`)\n    gives full access to the FTDI pins as raw I/O pins,\n  * ``SpiGpioPort`` (see :doc:`api/spi`) gives access to all free pins of an\n    FTDI interface, which are not reserved for the SPI feature,\n  * ``I2cGpioPort`` (see :doc:`api/i2c`) gives access to all free pins of an\n    FTDI interface, which are not reserved for the I2C feature\n\nOther modes\n```````````\n\n  * Gpio raw access is not yet supported with JTAG feature.\n  * It is not possible to use GPIO along with UART mode on the same interface.\n    However, UART mode still provides (very) limited access to GPIO pins, see\n    UART :ref:`uart_gpio` for details.\n\nThis document presents the common definitions for these APIs and explain how to\ndrive those pins.\n\n\nDefinitions\n~~~~~~~~~~~\n\nInterfaces\n``````````\n\nAn FTDI *interface* follows the definition of a *USB interface*: it is an\nindependent hardware communication port with an FTDI device. Each interface can\nbe configured independently from the other interfaces on the same device, e.g.\none interface may be configured as an UART, the other one as |I2C| + GPIO.\n\nIt is possible to access two distinct interfaces of the same FTDI device\nfrom a multithreaded application, and even from different applications, or\nPython interpreters. However two applications cannot access the same interface\nat the same time.\n\n.. warning::\n\n   Performing a USB device reset affects all the interfaces of an FTDI device,\n   this is the rationale for not automatically performing a device reset when\n   an interface is initialiazed and configured from PyFtdi_.\n\n.. _ftdi_ports:\n\nPorts\n`````\n\nAn FTDI port is ofter used in PyFtdi as a synonym for an interface. This may\ndiffer from the FTDI datasheets that sometimes show an interface with several\nports (A\\*BUS, B\\*BUS). From a software standpoint, ports and interfaces are\nequivalent: APIs access all the HW port from the same interface at once. From a\npure hardware standpoint, a single interface may be depicted as one or two\n*ports*.\n\nWith PyFtdi_, *ports* and *interfaces* should be considered as synomyms.\n\nEach port can be accessed as raw input/output pins. At a given time, a pin is\neither configured as an input or an output function.\n\nThe width of a port, that is the number of pins of the interface, depending on\nthe actual hardware, *i.e.* the FTDI model:\n\n* FT232R features a single port, which is 8-bit wide: `DBUS`,\n* FT232H features a single port, which is 16-bit wide: `ADBUS/ACBUS`,\n* FT2232D features two ports, which are 12-bit wide each: `ADBUS/ACBUS` and\n  `BDBUS/BCBUS`,\n* FT2232H features two ports, which are 16-bit wide each: `ADBUS/ACBUS` and\n  `BDBUS/BCBUS`,\n* FT4232H/FT4232HA features four ports, which are 8-bit wide each: `ADBUS`,\n  `BDBUS`, `CDBUS` and `DDBUS`,\n* FT230X features a single port, which is 4-bit wide,\n* FT231X feature a single port, which is 8-bit wide\n\nFor historical reasons, 16-bit ports used to be named *wide* ports and 8-bit\nports used to be called *narrow* with PyFtdi_. This terminology and APIs are\nno longer used, but are kept to prevent API break. Please only use the port\n``width`` rather than these legacy port types.\n\n\nGPIO value\n``````````\n\n* A logical ``0`` bit represents a low level value on a pin, that is *GND*\n* A logical ``1`` bit represents a high level value on a pin, that is *Vdd*\n  which is typically 3.3 volts on most FTDIs\n\nPlease refers to the FTDI datasheet of your device for the tolerance and\nsupported analog levels for more details\n\n.. hint::\n\n   FT232H supports a specific feature, which is dedicated to better supporting\n   the |I2C| feature. This specific devices enables an open-collector mode:\n\n   * Setting a pin to a low level drains it to *GND*\n   * Setting a pin to a high level sets the pin as High-Z\n\n   This feature is automatically activated when |I2C| feature is enabled on a\n   port, for the two first pins, i.e. `SCL` and `SDA out`.\n\n   However, PyFTDI does not yet provide an API to enable this mode to the\n   other pins of a port, *i.e.* for the pins used as GPIOs.\n\n\nDirection\n`````````\n\nAn FTDI pin should either be configured as an input or an ouput. It is\nmandatory to (re)configure the direction of a pin before changing the way it is\nused.\n\n* A logical ``0`` bit represents an input pin, *i.e.* a pin whose value can be\n  sampled and read via the PyFTDI APIs\n* A logical ``1`` bit represents an output pin, *i.e.* a pin whose value can be\n  set/written with the PyFTDI APIs\n\n\n.. _cbus_gpio:\n\nCBUS GPIOs\n~~~~~~~~~~\n\nFT232R, FT232H and FT230X/FT231X support an additional port denoted CBUS:\n\n* FT232R provides an additional 5-bit wide port, where only 4 LSBs can be\n  used as programmable GPIOs: ``CBUS0`` to ``CBUS3``,\n* FT232H provices an additional 10-bit wide port, where only 4 pins can be\n  used as programmable GPIOs: ``CBUS5``, ``CBUS6``, ``CBUS8``, ``CBUS9``\n* FT230X/FT231X provides an additional 4-bit wide port: ``CBUS0`` to ``CBUS3``\n\nNote that CBUS access is slower than regular asynchronous bitbang mode.\n\nCBUS EEPROM configuration\n`````````````````````````\n\nAccessing this extra port requires a specific EEPROM configuration.\n\nThe EEPROM needs to be configured so that the CBUS pins that need to be used\nas GPIOs are defined as ``GPIO``. Without this special configuration, CBUS\npins are used for other functions, such as driving leds when data is exchanged\nover the UART port. Remember to power-cycle the FTDI device after changing its\nEEPROM configuration to force load the new configuration.\n\nThe :ref:`ftconf` tool can be used to query and change the EEPROM\nconfiguration. See the EEPROM configuration :ref:`example <eeprom_cbus>`.\n\nCBUS GPIO API\n`````````````\n\nPyFtdi_ starting from v0.47 supports CBUS pins as special GPIO port. This port\nis *not* mapped as regular GPIO, a dedicated API is reserved to drive those\npins:\n\n* :py:meth:`pyftdi.ftdi.Ftdi.has_cbus` to report whether the device supports\n  CBUS gpios,\n* :py:meth:`pyftdi.ftdi.Ftdi.set_cbus_direction` to configure the port,\n* :py:meth:`pyftdi.ftdi.Ftdi.get_cbus_gpio` to get the logical values from the\n  port,\n* :py:meth:`pyftdi.ftdi.Ftdi.set_cbus_gpio` to set new logical values to the\n  port\n\nAdditionally, the EEPROM configuration can be queried to retrieve which CBUS\npins have been assigned to GPIO functions:\n\n* :py:meth:`pyftdi.eeprom.FtdiEeprom.cbus_pins` to report CBUS GPIO pins\n\nThe CBUS port is **not** available through the\n:py:class:`pyftdi.gpio.GpioController` API, as it cannot be considered as a\nregular GPIO port.\n\n.. warning::\n\n  CBUS GPIO feature has only be tested with the virtual test framework and a\n  real FT231X HW device. It should be considered as an experimental feature\n  for now.\n\nConfiguration\n~~~~~~~~~~~~~\n\nGPIO bitmap\n```````````\n\nThe GPIO pins of a port are always accessed as an integer, whose supported\nwidth depends on the width of the port. These integers should be considered as\na bitmap of pins, and are always assigned the same mapping, whatever feature is\nenabled:\n\n* b\\ :sub:`0`\\  (``0x01``) represents the first pin of a port, *i.e.* AD0/BD0\n* b\\ :sub:`1`\\  (``0x02``) represents the second pin of a port, *i.e.* AD1/BD1\n* ...\n* b\\ :sub:`7`\\  (``0x80``) represents the eighth pin of a port, *i.e.* AD7/BD7\n* b\\ :sub:`N`\\  represents the highest pin of a port, *i.e.* AD7/BD7 for an\n  8-bit port, AD15/BD15 for a 16-bit port, etc.\n\nPins reserved for a specific feature (|I2C|, SPI, ...) cannot be accessed as\na regular GPIO. They cannot be arbitrarily written and should be masked out\nwhen the GPIO output value is set. See :ref:`reserved_pins` for details.\n\nFT232H CBUS exception\n.....................\n\nNote that there is an exception to this rule for FT232H CBUS port: FTDI has\ndecided to map non-contiguous CBUS pins as GPIO-capable CBUS pins, that is\n``CBUS5``, ``CBUS6``, ``CBUS8``, ``CBUS9``, where other CBUS-enabled devices\nuse ``CBUS0``, ``CBUS1``, ``CBUS2``, ``CBUS3``.\n\nIf the CBUS GPIO feature is used with an FT232H device, the pin positions for\nthe GPIO port are not b\\ :sub:`5`\\  .. b\\ :sub:`9`\\  but b\\ :sub:`0`\\  to\nb\\ :sub:`3`\\  . This may sounds weird, but CBUS feature is somewhat hack-ish\neven with FTDI commands, so it did not deserve a special treatment for the sake\nof handling the weird implementation of FT232H.\n\nDirection bitmap\n````````````````\n\nBefore using a port as GPIO, the port must be configured as GPIO. This is\nachieved by either instanciating one of the *GpioController* or by requesting\nthe GPIO port from a specific serial bus controller:\n``I2cController.get_gpio()`` and ``SpiController.get_gpio()``. All instances\nprovide a similar API (duck typing API) to configure, read and write to GPIO\npins.\n\nOnce a GPIO port is instanciated, the direction of each pin should be defined.\nThe direction can be changed at any time. It is not possible to write to /\nread from a pin before the proper direction has been defined.\n\nTo configure the direction, use the `set_direction` API with a bitmap integer\nvalue that defines the direction to use of each pin.\n\nDirection example\n.................\n\nA 8-bit port, dedicated to GPIO, is configured as follows:\n\n * BD0, BD3, BD7: input, `I` for short\n * BD1-BD2, BD4-BD6: output, `O` for short\n\nThat is, MSB to LSB: *I O O O I O O I*.\n\nThis translates to 0b ``0111 0110`` as output is ``1`` and input is ``0``,\nthat is ``0x76`` as an hexa value. This is the direction value to use to\n``configure()`` the port.\n\nSee also the ``set_direction()`` API to reconfigure the direction of GPIO pins\nat any time. This method accepts two arguments. This first arguments,\n``pins``, defines which pins - the ones with the maching bit set - to consider\nin the second ``direction`` argument, so there is no need to\npreserve/read-modify-copy the configuration of other pins. Pins with their\nmatching bit reset are not reconfigured, whatever their direction bit.\n\n.. code-block:: python\n\n    gpio = GpioAsyncController()\n    gpio.configure('ftdi:///1', direction=0x76)\n    # later, reconfigure BD2 as input and BD7 as output\n    gpio.set_direction(0x84, 0x80)\n\n\nUsing GPIO APIs\n~~~~~~~~~~~~~~~\n\nThere are 3 variant of *GpioController*, depending on which features are needed\nand how the GPIO port usage is intended. :doc:`api/gpio` gives in depth details\nabout those controllers. Those controllers are mapped onto FTDI HW features.\n\n* ``GpioAsyncController`` is likely the most useful API to drive GPIOs.\n\n  It enables reading current GPIO input pin levels and to change GPIO output\n  pin levels. When vector values (byte buffers) are used instead of scalar\n  value (single byte), GPIO pins are samples/updated at a regular pace, whose\n  frequency can be configured. It is however impossible to control the exact\n  time when input pins start to be sampled, which can be tricky to use with\n  most applications. See :doc:`api/gpio` for details.\n\n* ``GpioSyncController`` is a variant of the previous API.\n\n  It is aimed at precise time control of sampling/updating the GPIO: a new\n  GPIO input sample is captured once every time GPIO output pins are updated.\n  With byte buffers, GPIO pins are samples/updated at a regular pace, whose\n  frequency can be configured as well. The API of ``GpioSyncController``\n  slightly differ from the other GPIO APIs, as the usual ``read``/``write``\n  method are replaced with a single ``exchange`` method.\n\nBoth ``GpioAsyncController`` and ``GpioSyncController`` are restricted to only\naccess the 8 LSB pins of a port, which means that FTDI device with wider port\n(12- and 16- pins) cannot be fully addressed, as only b\\ :sub:`0`\\  to b\\\n:sub:`7`\\  can be addressed.\n\n* ``GpioMpsseController`` enables access to the MSB pins of wide ports.\n\n  However LSB and MSB pins cannot be addressed in a true atomic manner, which\n  means that there is a short delay between sampling/updating the LSB and MSB\n  part of the same wide port. Byte buffer can also be sampled/updated at a\n  regular pace, but the achievable frequency range may differ from the other\n  controllers.\n\nIt is recommened to read the ``tests/gpio.py`` files - available from GitHub -\nto get some examples on how to use these API variants.\n\nSetting GPIO pin state\n``````````````````````\n\nTo write to a GPIO, use the `write()` method. The caller needs to mask out\nthe bits configured as input, or an exception is triggered:\n\n* writing ``0`` to an input pin is ignored\n* writing ``1`` to an input pin raises an exception\n\n.. code-block:: python\n\n    gpio = GpioAsyncController()\n    gpio.configure('ftdi:///1', direction=0x76)\n    # all output set low\n    gpio.write(0x00)\n    # all output set high\n    gpio.write(0x76)\n    # all output set high, apply direction mask\n    gpio.write(0xFF & gpio.direction)\n    # all output forced to high, writing to input pins is illegal\n    gpio.write(0xFF)  # raises an IOError\n    gpio.close()\n\n\nRetrieving GPIO pin state\n`````````````````````````\n\nTo read a GPIO, use the `read()` method.\n\n.. code-block:: python\n\n    gpio = GpioAsyncController()\n    gpio.configure('ftdi:///1', direction=0x76)\n    # read whole port\n    pins = gpio.read()\n    # ignore output values (optional)\n    pins &= ~gpio.direction\n    gpio.close()\n\n\nModifying GPIO pin state\n````````````````````````\n\nA read-modify-write sequence is required.\n\n.. code-block:: python\n\n    gpio = GpioAsyncController()\n    gpio.configure('ftdi:///1', direction=0x76)\n    # read whole port\n    pins = gpio.read()\n    # clearing out AD1 and AD2\n    pins &= ~((1 << 1) | (1 << 2))  # or 0x06\n    # want AD2=0, AD1=1\n    pins |= 1 << 1\n    # update GPIO output\n    gpio.write(pins)\n    gpio.close()\n\n\nSynchronous GPIO access\n```````````````````````\n\n.. code-block:: python\n\n    gpio = GpioSyncController()\n    gpio.configure('ftdi:///1', direction=0x0F, frequency=1e6)\n    outs = bytes(range(16))\n    ins = gpio.exchange(outs)\n    # ins contains as many bytes as outs\n    gpio.close()\n\n\nCBUS GPIO access\n````````````````\n\n.. code-block:: python\n\n   ftdi = Ftdi()\n   ftdi.open_from_url('ftdi:///1')\n   # validate CBUS feature with the current device\n   assert ftdi.has_cbus\n   # validate CBUS EEPROM configuration with the current device\n   eeprom = FtdiEeprom()\n   eeprom.connect(ftdi)\n   # here we use CBUS0 and CBUS3 (or CBUS5 and CBUS9 on FT232H)\n   assert eeprom.cbus_mask & 0b1001 == 0b1001\n   # configure CBUS0 as output and CBUS3 as input\n   ftdi.set_cbus_direction(0b1001, 0b0001)\n   # set CBUS0\n   ftdi.set_cbus_gpio(0x1)\n   # get CBUS3\n   cbus3 = ftdi.get_cbus_gpio() >> 3\n\n\n.. code-block:: python\n\n   # it is possible to open the ftdi object from an existing serial connection:\n   port = serial_for_url('ftdi:///1')\n   ftdi = port.ftdi\n   ftdi.has_cbus\n   # etc...\n\n.. _reserved_pins:\n\nReserved pins\n~~~~~~~~~~~~~\n\nGPIO pins vs. feature pins\n``````````````````````````\n\nIt is important to note that the reserved pins do not change the pin\nassignment, *i.e.* the lowest pins of a port may become unavailable as regular\nGPIO when the feature is enabled:\n\nExample\n.......\n\n|I2C| feature reserves\nthe three first pins, as *SCL*, *SDA output*, *SDA input* (w/o clock stretching\nfeature which also reserves another pin). This means that AD0, AD1 and AD2,\nthat is b\\ :sub:`0`\\ , b\\ :sub:`1`\\ , b\\ :sub:`2`\\  cannot be directly\naccessed.\n\nThe first accessible GPIO pin in this case is no longer AD0 but AD3, which\nmeans that b\\ :sub:`3`\\ becomes the lowest bit which can be read/written.\n\n.. code-block:: python\n\n    # use I2C feature\n    i2c = I2cController()\n    # configure the I2C feature, and predefines the direction of the GPIO pins\n    i2c.configure('ftdi:///1', direction=0x78)\n    gpio = i2c.get_gpio()\n    # read whole port\n    pins = gpio.read()\n    # clearing out I2C bits (SCL, SDAo, SDAi)\n    pins &= 0x07\n    # set AD4\n    pins |= 1 << 4\n    # update GPIO output\n    gpio.write(pins)\n"
  },
  {
    "path": "pyftdi/doc/index.rst",
    "content": "PyFtdi\n======\n\n.. cannot use defs.rst here, as PyPi wants a standalone file.\n.. |I2C| replace:: I\\ :sup:`2`\\ C\n\nDocumentation\n-------------\n\nThe latest PyFtdi online documentation is always available from\n`here <https://eblot.github.io/pyftdi>`_.\n\nBeware the online version may be more recent than the PyPI hosted version, as\nintermediate development versions are not published to\n`PyPi <https://pypi.org/project/pyftdi>`_.\n\nPyFtdi documentation can be locally build with Sphinx, see the installation\ninstructions.\n\nSource code\n-----------\n\nPyFtdi releases are available from the Python Package Index from\n`PyPi <https://pypi.org/project/pyftdi>`_.\n\nPyFtdi development code is available from\n`GitHub <https://github.com/eblot/pyftdi>`_.\n\nOverview\n--------\n\nPyFtdi aims at providing a user-space driver for popular FTDI devices,\nimplemented in pure Python language.\n\nSupported FTDI devices include:\n\n* UART and GPIO bridges\n\n  * FT232R (single port, 3Mbps)\n  * FT230X/FT231X/FT234X (single port, 3Mbps)\n\n* UART and multi-serial protocols (SPI, |I2C|, JTAG) bridges\n\n  * FT2232C/D (dual port, clock up to 6 MHz)\n  * FT232H (single port, clock up to 30 MHz)\n  * FT2232H (dual port, clock up to 30 MHz)\n  * FT4232H (quad port, clock up to 30 MHz)\n  * FT4232HA (quad port, clock up to 30 MHz)\n\nFeatures\n--------\n\nPyFtdi currently supports the following features:\n\n* UART/Serial USB converter, up to 12Mbps (depending on the FTDI device\n  capability)\n* GPIO/Bitbang support, with 8-bit asynchronous, 8-bit synchronous and\n  8-/16-bit MPSSE variants\n* SPI master, with simultanous GPIO support, up to 12 pins per port,\n  with support for non-byte sized transfer\n* |I2C| master, with simultanous GPIO support, up to 14 pins per port\n* Basic JTAG master capabilities\n* EEPROM support (some parameters cannot yet be modified, only retrieved)\n* Experimental CBUS support on selected devices, 4 pins per port\n\nSupported host OSes\n-------------------\n\n* macOS\n* Linux\n* FreeBSD\n* Windows, although not officially supported\n\n.. EOT\n\nWarning\n-------\n\nStarting with version *v0.40.0*, several API changes are being introduced.\nWhile PyFtdi tries to maintain backward compatibility with previous versions,\nsome of these changes may require existing clients to update calls to PyFtdi.\n\nDo not upgrade to *v0.40.0* or above without testing your client against the\nnew PyFtdi releases. PyFtdi versions up to *v0.39.9* keep a stable API\nwith *v0.22+* series.\n\nSee the *Major Changes* section on the online documentation for details about\npotential API breaks.\n\n\nMajor changes\n~~~~~~~~~~~~~\n\n * *read* methods now return ``bytearray`` instead of `Array('B')` so that\n   pyserial ``readline()`` may be used. It also brings some performance\n   improvements.\n * PyFtdi URLs now supports ``bus:address`` alternative specifiers, which\n   required to augment the ``open_*()`` methods with new, optional parameters.\n * ``SpiController`` reserves only one slave line (*/CS*) where it used to\n   reserve 4 slave lines in previous releases. This frees more GPIOs when\n   default value is used - it is nevertheless still possible to reserve up to 5\n   slave lines.\n * type hinting is used for most, if not all, public methods.\n * simplified baudrate divider calculation.\n\nPyFTDI in details\n-----------------\n\n.. toctree::\n   :maxdepth: 1\n   :glob:\n\n   features\n   requirements\n   installation\n   urlscheme\n   tools\n   api/index\n   pinout\n   gpio\n   eeprom\n   testing\n   troubleshooting\n   authors\n   license\n"
  },
  {
    "path": "pyftdi/doc/installation.rst",
    "content": ".. include:: defs.rst\n\nInstallation\n------------\n\nPrerequisites\n~~~~~~~~~~~~~\n\nPyFTDI_ relies on PyUSB_, which requires a native dependency: libusb 1.x.\n\nThe actual command to install depends on your OS and/or your distribution,\nsee below\n\n.. _install_linux:\n\nDebian/Ubuntu Linux\n```````````````````\n\n.. code-block:: shell\n\n     apt-get install libusb-1.0\n\nOn Linux, you also need to create a `udev` configuration file to allow\nuser-space processes to access to the FTDI devices. There are many ways to\nconfigure `udev`, here is a typical setup:\n\n::\n\n    # /etc/udev/rules.d/11-ftdi.rules\n\n    # FT232AM/FT232BM/FT232R\n    SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0403\", ATTR{idProduct}==\"6001\", GROUP=\"plugdev\", MODE=\"0664\"\n    # FT2232C/FT2232D/FT2232H\n    SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0403\", ATTR{idProduct}==\"6010\", GROUP=\"plugdev\", MODE=\"0664\"\n    # FT4232/FT4232H\n    SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0403\", ATTR{idProduct}==\"6011\", GROUP=\"plugdev\", MODE=\"0664\"\n    # FT232H\n    SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0403\", ATTR{idProduct}==\"6014\", GROUP=\"plugdev\", MODE=\"0664\"\n    # FT230X/FT231X/FT234X\n    SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0403\", ATTR{idProduct}==\"6015\", GROUP=\"plugdev\", MODE=\"0664\"\n    # FT4232HA\n    SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0403\", ATTR{idProduct}==\"6048\", GROUP=\"plugdev\", MODE=\"0664\"\n\n.. note:: **Accessing FTDI devices with custom VID/PID**\n\n   You need to add a line for each device with a custom VID / PID pair you\n   declare, see :ref:`custom_vid_pid` for details.\n\nYou need to unplug / plug back the FTDI device once this file has been\ncreated so that `udev` loads the rules for the matching device, or\nalternatively, inform the ``udev`` daemon about the changes:\n\n.. code-block:: shell\n\n   sudo udevadm control --reload-rules\n   sudo udevadm trigger\n\nWith this setup, be sure to add users that want to run PyFtdi_ to the\n`plugdev` group, *e.g.*\n\n.. code-block:: shell\n\n    sudo adduser $USER plugdev\n\nRemember that you need to log out / log in to get the above command\neffective, or start a subshell to try testing PyFtdi_:\n\n.. code-block:: shell\n\n    newgrp plugdev\n\n\n.. _install_macos:\n\nHomebrew macOS\n``````````````\n\n.. code-block:: shell\n\n    brew install libusb\n\n\n.. _install_windows:\n\nWindows\n```````\n\nWindows is not officially supported (*i.e.* not tested) but some users have\nreported successful installations. Windows requires a specific libusb backend\ninstallation.\n\nZadig\n.....\n\nThe probably easiest way to deal with libusb on Windows is to use Zadig_\n\n1. Start up the Zadig utility\n\n2. Select ``Options/List All Devices``, then select the FTDI devices you want\n   to communicate with. Its names depends on your hardware, *i.e.* the name\n   stored in the FTDI EEPROM.\n\n  * With FTDI devices with multiple channels, such as FT2232 (2 channels) and\n    FT4232 (4 channels), you **must** install the driver for the composite\n    parent, **not** for the individual interfaces. If you install the driver\n    for each interface, each interface will be presented as a unique FTDI\n    device and you may have difficulties to select a specific FTDI device port\n    once the installation is completed. To make the composite parents to appear\n    in the device list, uncheck the ``Options/Ignore Hubs or Composite Parents``\n    menu item.\n\n  * Be sure to select the parent device, *i.e.* the device name should not end\n    with *(Interface N)*, where *N* is the channel number.\n\n    * for example *Dual RS232-HS* represents the composite parent, while\n      *Dual RS232-HS (Interface 0)* represents a single channel of the FTDI\n      device. Always select the former.\n\n3. Select ``libusb-win32`` (not ``WinUSB``) in the driver list.\n\n4. Click on ``Replace Driver``\n\nSee also `Libusb on Windows`_\n\n\n.. _install_python:\n\nPython\n~~~~~~\n\nPython dependencies\n```````````````````\n\nDependencies should be automatically installed with PIP.\n\n  * pyusb >= 1.0.0, != 1.2.0\n  * pyserial >= 3.0\n\nDo *not* install PyUSB_ from GitHub development branch (``master``, ...).\nAlways prefer a stable, tagged release.\n\nPyUSB 1.2.0 also broke the backward compatibility of the Device API, so it will\nnot work with PyFtdi.\n\nInstalling with PIP\n```````````````````\n\nPIP should automatically install the missing dependencies.\n\n.. code-block:: shell\n\n     pip3 install pyftdi\n\n\n.. _install_from_source:\n\nInstalling from source\n``````````````````````\n\nIf you prefer to install from source, check out a fresh copy from PyFtdi_\ngithub repository.\n\n.. code-block:: shell\n\n     git clone https://github.com/eblot/pyftdi.git\n     cd pyftdi\n     # note: 'pip3' may simply be 'pip' on some hosts\n     pip3 install -r requirements.txt\n     python3 setup.py install\n\n\n.. _generate_doc:\n\nGenerating the documentation\n````````````````````````````\n\nFollow :ref:`install_from_source` then:\n\n.. code-block:: shell\n\n     pip3 install setuptools wheel sphinx sphinx_autodoc_typehints\n     # Shpinx Read the Doc theme seems to never get a release w/ fixed issues\n     pip3 install -U -e git+https://github.com/readthedocs/sphinx_rtd_theme.git@2b8717a3647cc650625c566259e00305f7fb60aa#egg=sphinx_rtd_theme\n     sphinx-build -b html pyftdi/doc .\n\nThe documentation may be accessed from the generated ``index.html`` entry file.\n\n\nPost-installation sanity check\n``````````````````````````````\n\nOpen a *shell*, or a *CMD* on Windows\n\n.. code-block:: shell\n\n    python3  # or 'python' on Windows\n    from pyftdi.ftdi import Ftdi\n    Ftdi.show_devices()\n\nshould list all the FTDI devices available on your host.\n\nAlternatively, you can invoke ``ftdi_urls.py`` script that lists all detected\nFTDI devices. See the :doc:`tools` chapter for details.\n\n  * Example with 1 FT232H device with a serial number and 1 FT2232 device\n    with no serial number, connected to the host:\n\n    .. code-block::\n\n        Available interfaces:\n          ftdi://ftdi:232h:FT1PWZ0Q/1   (C232HD-DDHSP-0)\n          ftdi://ftdi:2232/1            (Dual RS232-HS)\n          ftdi://ftdi:2232/2            (Dual RS232-HS)\n\n\nNote that FTDI devices with custom VID/PID are not listed with this simple\ncommand, please refer to the PyFtdi_ API to add custom identifiers, *i.e.* see\n:py:meth:`pyftdi.ftdi.Ftdi.add_custom_vendor` and\n:py:meth:`pyftdi.ftdi.Ftdi.add_custom_product` APIs.\n\n\n.. _custom_vid_pid:\n\nCustom USB vendor and product IDs\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nPyFtdi only recognizes FTDI official vendor and product IDs.\n\nIf you have an FTDI device with an EEPROM with customized IDs, you need to tell\nPyFtdi to support those custom USB identifiers.\n\nCustom PID\n``````````\n\nTo support a custom product ID (16-bit integer) with the official FTDI ID, add\nthe following code **before** any call to an FTDI ``open()`` method.\n\n.. code-block:: python\n\n   from pyftdi.ftdi import Ftdi\n\n   Ftdi.add_custom_product(Ftdi.DEFAULT_VENDOR, product_id)\n\nCustom VID\n``````````\n\nTo support a custom vendor ID and product ID (16-bit integers), add the\nfollowing code **before** any call to an FTDI ``open()`` method.\n\n.. code-block:: python\n\n   from pyftdi.ftdi import Ftdi\n\n   Ftdi.add_custom_vendor(vendor_id)\n   Ftdi.add_custom_product(vendor_id, product_id)\n\nYou may also specify an arbitrary string to each method if you want to specify\na URL by custom vendor and product names instead of their numerical values:\n\n.. code-block:: python\n\n   from pyftdi.ftdi import Ftdi\n\n   Ftdi.add_custom_vendor(0x1234, 'myvendor')\n   Ftdi.add_custom_product(0x1234, 0x5678, 'myproduct')\n\n   f1 = Ftdi.create_from_url('ftdi://0x1234:0x5678/1')\n   f2 = Ftdi.create_from_url('ftdi://myvendor:myproduct/2')\n\n.. note::\n\n   Remember that on OSes that require per-device access permissions such as\n   Linux, you also need to add the custom VID/PID entry to the configuration\n   file, see :ref:`Linux installation <install_linux>` ``udev`` rule file.\n"
  },
  {
    "path": "pyftdi/doc/license.rst",
    "content": "License\n-------\n\n.. include:: defs.rst\n\nFor historical reasons (PyFtdi has been initially developed as a compatibility\nlayer with libftdi_), the main ``ftdi.py`` file had originally been licensed\nunder the same license as the libftdi_ project, the GNU Lesser General Public\nLicense LGPL v2 license. It does not share code from this project anymore, but\nimplements a similar API.\n\nFrom my perspective, you may use it freely in open source or close source, free\nor commercial projects as long as you comply with the BSD 3-clause license.\n\n\nBSD 3-clause\n~~~~~~~~~~~~\n\n::\n\n  Copyright (c) 2008-2025 Emmanuel Blot <emmanuel.blot@free.fr>\n  All Rights Reserved.\n\n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions are met:\n      * Redistributions of source code must retain the above copyright\n        notice, this list of conditions and the following disclaimer.\n      * Redistributions in binary form must reproduce the above copyright\n        notice, this list of conditions and the following disclaimer in the\n        documentation and/or other materials provided with the distribution.\n      * Neither the name of the author nor the names of its contributors may\n        be used to endorse or promote products derived from this software\n        without specific prior written permission.\n\n  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n  ARE DISCLAIMED. IN NO EVENT SHALL NEOTION BE LIABLE FOR ANY DIRECT, INDIRECT,\n  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,\n  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\n  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "pyftdi/doc/pinout.rst",
    "content": ".. include:: defs.rst\n\nFTDI device pinout\n------------------\n\n============ ============= ======= ====== ============== ========== ====== =============\n IF/1 [#ih]_ IF/2 [#if2]_  BitBang  UART   |I2C|          SPI        JTAG   C232HD cable\n============ ============= ======= ====== ============== ========== ====== =============\n ``ADBUS0``   ``BDBUS0``   GPIO0    TxD    SCK            SCLK       TCK   Orange\n ``ADBUS1``   ``BDBUS1``   GPIO1    RxD    SDA/O [#i2c]_  MOSI       TDI   Yellow\n ``ADBUS2``   ``BDBUS2``   GPIO2    RTS    SDA/I [#i2c]_  MISO       TDO   Green\n ``ADBUS3``   ``BDBUS3``   GPIO3    CTS    GPIO3          CS0        TMS   Brown\n ``ADBUS4``   ``BDBUS4``   GPIO4    DTR    GPIO4          CS1/GPIO4        Grey\n ``ADBUS5``   ``BDBUS5``   GPIO5    DSR    GPIO5          CS2/GPIO5        Purple\n ``ADBUS6``   ``BDBUS6``   GPIO6    DCD    GPIO6          CS3/GPIO6        White\n ``ADBUS7``   ``BDBUS7``   GPIO7    RI     RSCK [#rck]_   CS4/GPIO7  RCLK  Blue\n ``ACBUS0``   ``BCBUS0``                   GPIO8          GPIO8\n ``ACBUS1``   ``BCBUS1``                   GPIO9          GPIO9\n ``ACBUS2``   ``BCBUS2``                   GPIO10         GPIO10\n ``ACBUS3``   ``BCBUS3``                   GPIO11         GPIO11\n ``ACBUS4``   ``BCBUS4``                   GPIO12         GPIO12\n ``ACBUS5``   ``BCBUS5``                   GPIO13         GPIO13\n ``ACBUS6``   ``BCBUS6``                   GPIO14         GPIO14\n ``ACBUS7``   ``BCBUS7``                   GPIO15         GPIO15\n============ ============= ======= ====== ============== ========== ====== =============\n\n.. [#ih]  16-bit port (ACBUS, BCBUS) is not available with FT4232H_ series, and\n          FTDI2232C/D only support 12-bit ports.\n.. [#i2c] FTDI pins are either configured as input or output. As |I2C| SDA line\n          is bi-directional, two FTDI pins are required to provide the SDA\n          feature, and they should be connected together and to the SDA |I2C|\n          bus line. Pull-up resistors on SCK and SDA lines should be used.\n.. [#if2] FT232H_ does not support a secondary MPSSE port, only FT2232H_,\n          FT4232H_ and FT4232HA_ do. Note that FT4232H_/FT4232HA_ has 4 serial\n          ports, but only the first two interfaces are MPSSE-capable. C232HD\n          cable only exposes IF/1 (ADBUS).\n.. [#rck] In order to support I2C clock stretch mode, ADBUS7 should be\n          connected to SCK. When clock stretching mode is not selected, ADBUS7\n          may be used as GPIO7."
  },
  {
    "path": "pyftdi/doc/requirements.rst",
    "content": ".. include:: defs.rst\n\nRequirements\n------------\n\nPython_ 3.9 or above is required.\n\nPyFtdi_ relies on PyUSB_, which itself depends on one of the following native\nlibraries:\n\n* libusb_, currently tested with 1.0.23\n\nPyFtdi_ does not depend on any other native library. It only uses standard\nPython modules, and PyUSB_, pyserial_ and PyJtagTools_.\n\nPyFtdi_ is being tested with PyUSB_ 1.2.1.\n\nDevelopment\n~~~~~~~~~~~\n\nPyFtdi_ is developed on macOS platforms (64-bit kernel), and is validated on a\nregular basis on Linux hosts.\n\nAs it contains no native code, it should work on any PyUSB_ and libusb_\nsupported platforms. However, M$ Windows is a seamless source of issues and is\nnot officially supported, although users have reported successful installation\nwith Windows 7 for example. Your mileage may vary.\n\nA fork of PyFtdi which relies on the official FTDI D2XX Windows library might be\na better solution for Windows users, please check out PyFtdiWin_.\n\nAPI breaks\n~~~~~~~~~~\n\nStarting with version *v0.40.0*, several API changes are being introduced.\nWhile PyFtdi tries to maintain backward compatibility with previous versions,\nsome of these changes may require existing clients to update calls to PyFtdi.\n\nDo not upgrade to *v0.40.0* or above without testing your client against the\nnew PyFtdi releases. PyFtdi versions up to *v0.39.9* keep a stable API\nwith *v0.22+* series.\n\nSee the *Major Changes* section for details about potential API breaks.\n\nLegacy Python support\n~~~~~~~~~~~~~~~~~~~~~\n\n* PyFtdi *v0.55* is the last PyFtdi version to support Python 3.8.\n\n  * Python 3.8 has reached end-of-life on October 7th, 2024.\n\n* PyFtdi *v0.54* is the last PyFtdi version to support Python 3.7.\n\n  * Python 3.7 has reached end-of-life on June 27rd, 2023.\n\n* PyFtdi *v0.53* is the last PyFtdi version to support Python 3.6.\n\n  * Python 3.6 has reached end-of-life on December 23rd, 2021.\n\n* PyFtdi *v0.52* is the last PyFtdi version to support Python 3.5.\n\n  * Python 3.5 has reached end-of-life on September 5th, 2020.\n"
  },
  {
    "path": "pyftdi/doc/testing.rst",
    "content": "Testing\n-------\n\n.. include:: defs.rst\n\nOverview\n~~~~~~~~\n\nTesting PyFTDI is challenging because it relies on several pieces of hardware:\n\n* one or more FTDI device\n* |I2C|, SPI, JTAG bus slaves or communication equipment for UART\n\nThe ``tests`` directory contain several tests files, which are primarily aimed\nat demonstrating usage of PyFTDI in common use cases.\n\nMost unit tests are disabled, as they require specific slaves, with a dedicated\nHW wiring. Reproducing such test environments can be challenging, as it\nrequires dedicated test benchs.\n\nThis is a growing concern as PyFTDI keeps evolving, and up to now, regression\ntests were hard to run.\n\nHardware tests\n~~~~~~~~~~~~~~\n\nPlease refer to the ``pyftdi/tests`` directory. There is one file dedicated to\neach feature to test. Note that you need to read and edit these tests files to\nfit your actual test environment, and enable the proper unit test cases, as\nmost are actually disabled by default.\n\nYou need specific bus slaves to perform most of these tests.\n\n.. _virtual_framework:\n\nVirtual test framework\n~~~~~~~~~~~~~~~~~~~~~~\n\nWith PyFTDI v0.45, a new test module enables PyFTDI API partial testing using a\npure software environment with no hardware. This also eases automatic testing\nwithin a continuous integration environment.\n\nThis new module implements a virtual USB backend for PyUSB, which creates some\nkind of virtual, limited USB stack. The PyUSB can be told to substitute the\nnative platform's libusb with this module.\n\nThis module, ``usbvirt`` can be dynamically confifured with the help of YaML\ndefinition files to create one or more virtual FTDI devices on a virtual USB\nbus topology. This enables to test ``usbtools`` module to enumerate, detect,\nreport and access FTDI devices using the regular :doc:`urlscheme` syntax.\n\n``usbvirt`` also routes all vendor-specific USB API calls to a secondary\n``ftdivirt`` module, which is in charge of handling all FTDI USB requests.\n\nThis module enables testing PyFtdi_ APIs. It also re-uses the MPSSE tracker\nengine to decode and verify MPSSE requests used to support |I2C|, SPI and UART\nfeatures.\n\nFor now, it is able to emulate most of GPIO requests (async, sync and MPSSE)\nand UART input/output. It also manages the frequency and baudrate settings.\n\nIt is not able to emulate the MPSSE commands (with the exception of set and get\nGPIO values), as it represents a massive workload...\n\nBeware: WIP\n```````````\n\nThis is an experimental work in progress, which is its early inception stage.\n\nIt has nevertheless already revealed a couple of bugs that had been hiding\nwithin PyFtdi_ for years.\n\nThere is a large work effort ahead to be able to support more use cases and\ntests more APIs, and many unit tests to write.\n\nIt cannot replace hardware tests with actual boards and slaves, but should\nsimplify test setup and help avoiding regression issues.\n\n\nUsage\n`````\n\nNo hardware is required to run these tests, to even a single FTDI device.\n\nThe test configuration files are described in YaML file format, therefore the\nruamel.yaml_ package is required.\n\n.. code-block:: python\n\n    pip3 install ruamel.yaml\n    PYTHONPATH=. FTDI_LOGLEVEL=info pyftdi/tests/mockusb.py\n\nConfiguration\n`````````````\n\nThe ``pyftdi/tests/resources`` directory contains definition files which are\nloaded by the mock unit tests.\n\nAlthough it is possible to create fine grained USB device definitions, the\nconfiguration loader tries to automatically define missing parts to match the\nUSB device topology of FTDI devices.\n\nThis enables to create simple definition files without having to mess with low\nlevel USB definitions whenever possible.\n\nEEPROM content\n..............\n\nThe :ref:`ftconf` tool can be used to load, modify and generate the content of\na virtual EEPROM, see :doc:`eeprom`.\n\nExamples\n........\n\n * An example of a nearly comprehensive syntax can be found in ``ft232h.yaml``.\n * Another, much more simple example with only mandatory settings can be found\n   in ``ft230x.yaml``.\n * An example of multiple FTDI device definitions can be found in\n   ``ftmany.yaml``\n\n\nAvailability\n~~~~~~~~~~~~\n\nNote that unit tests and the virtual infrastructure are not included in the\ndistributed Python packages, they are only available from the git repository.\n"
  },
  {
    "path": "pyftdi/doc/tools.rst",
    "content": ".. include:: defs.rst\n\n.. _tools:\n\nTools\n-----\n\nOverview\n~~~~~~~~\n\nPyFtdi_ comes with a couple of scripts designed to help using PyFtdi_ APIs,\nand can be useful to quick start working with PyFtdi_.\n\nScripts\n~~~~~~~\n\n.. _ftdi_urls:\n\n``ftdi_urls``\n`````````````\n\nThis tiny script ``ftdi_urls.py`` to list the available, *i.e.* detected,\nFTDI devices connected to the host, and the URLs than can be used to open a\n:py:class:`pyftdi.ftdi.Ftdi` instance with the\n:py:class:`pyftdi.ftdi.Ftdi.open_from_url` family and ``configure`` methods.\n\n\n``ftconf``\n``````````\n\n``ftconf.py`` is a companion script to help managing the content of\nthe FTDI EEPROM from the command line. See the :ref:`ftconf` documentation.\n\n\n.. _i2cscan:\n\n``i2cscan``\n```````````\n\nThe ``i2cscan.py`` script helps to discover which I2C devices\nare connected to the FTDI I2C bus.\n\n\n.. _pyterm.py:\n\n``pyterm``\n``````````\n\n``pyterm.py`` is a simple serial terminal that can be used to test the serial\nport feature, see the :ref:`pyterm` documentation.\n\n\nWhere to find these tools?\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThese scripts can be downloaded from PyFtdiTools_, and are also installed along\nwith the PyFtdi_ module on the local host.\n\nThe location of the scripts depends on how PyFtdi_ has been installed and the\ntype of hosts:\n\n* on linux and macOS, there are located in the ``bin/`` directory, that is the\n  directory where the Python interpreter is installed.\n\n* on Windows, there are located in the ``Scripts/`` directory, which is a\n  subdirectory of the directory where the Python interpreter is installed.\n\n\n.. _common_option_switches:\n\nCommon options switches\n~~~~~~~~~~~~~~~~~~~~~~~\n\nPyFtdi_ tools share many common option switches:\n\n.. _option_d:\n\n``-d``\n  Enable debug mode, which emits Python traceback on exceptions\n\n.. _option_h:\n\n``-h``\n  Show quick help and exit\n\n.. _option_P_:\n\n``-P <vidpid>``\n  Add custom vendor and product identifiers.\n\n  PyFtdi_ only recognizes FTDI official USB vendor identifier (*0x403*) and\n  the USB identifiers of their products.\n\n  In order to use alternative VID/PID values, the PyFtdi_ tools accept the\n  ``-P`` option to describe those products\n\n  The ``vidpid`` argument should match the following format:\n\n  ``[vendor_name=]<vendor_id>:[product_name=]<product_id>``\n\n  * ``vendor_name`` and ``product_name`` are optional strings, they may be\n    omitted as they only serve as human-readable aliases for the vendor and\n    product names. See example below.\n  * ``vendor_id`` and ``product_id`` are mandatory strings that should resolve\n    into 16-bit integers (USB VID and PID values). Integer values are always\n    interpreted as hexadecimal values, *e.g.* `-P 1234:6789` is parsed as\n    `-P 0x1234:0x6789`.\n\n  This option may be repeated as many times as required to add support for\n  several custom devices.\n\n  examples:\n\n   * ``0x403:0x9999``, *vid:pid* short syntax, with no alias names;\n     a matching FTDI :ref:`URL <url_scheme>` would be ``ftdi://ftdi:0x9999/1``\n   * ``mycompany=0x666:myproduct=0xcafe``, *vid:pid* complete syntax with\n     aliases; matching FTDI :ref:`URLs <url_scheme>` could be:\n\n     * ``ftdi://0x666:0x9999/1``\n     * ``ftdi://mycompany:myproduct/1``\n     * ``ftdi://mycompany:0x9999/1``\n     * ...\n\n.. _option_v:\n\n``-v``\n  Increase verbosity, useful for debugging the tool. It can be repeated to\n  increase more the verbosity.\n\n.. _option_V_:\n\n``-V <virtual>``\n  Load a virtual USB device configuration, to use a virtualized FTDI/EEPROM\n  environment. This is useful for PyFtdi_ development, and to test EEPROM\n  configuration with a virtual setup. This option is not useful for regular\n  usage. See :ref:`virtual_framework`.\n\n"
  },
  {
    "path": "pyftdi/doc/troubleshooting.rst",
    "content": ".. include:: defs.rst\n\nTroubleshooting\n---------------\n\nReporting a bug\n~~~~~~~~~~~~~~~\n\nPlease do not contact the author by email. The preferered method to report bugs\nand/or enhancement requests is through\n`GitHub <https://github.com/eblot/pyftdi/issues>`_.\n\nPlease be sure to read the next sections before reporting a new issue.\n\nLogging\n~~~~~~~\n\nFTDI uses the `pyftdi` logger.\n\nIt emits log messages with raw payload bytes at DEBUG level, and data loss\nat ERROR level.\n\nCommon error messages\n~~~~~~~~~~~~~~~~~~~~~\n\n\"Error: No backend available\"\n`````````````````````````````\n\nlibusb native library cannot be loaded. Try helping the dynamic loader:\n\n* On Linux: ``export LD_LIBRARY_PATH=<path>``\n\n  where ``<path>`` is the directory containing the ``libusb-1.*.so``\n  library file\n\n* On macOS: ``export DYLD_LIBRARY_PATH=.../lib``\n\n  where ``<path>`` is the directory containing the ``libusb-1.*.dylib``\n  library file\n\n* On Windows:\n\n  Try to copy the USB dll where the Python executable is installed, along\n  with the other Python DLLs.\n\n  If this happens while using an exe created by pyinstaller:\n  ``copy C:\\Windows\\System32\\libusb0.dll <path>``\n\n  where ``<path>`` is the directory containing the executable created\n  by pyinstaller. This assumes you have installed libusb (using a tool\n  like Zadig) as referenced in the installation guide for Windows.\n\n\n\"Error: Access denied (insufficient permissions)\"\n`````````````````````````````````````````````````\n\nThe system may already be using the device.\n\n* On macOS: starting with 10.9 \"*Mavericks*\", macOS ships with a native FTDI\n  kernel extension that preempts access to the FTDI device.\n\n  Up to 10.13 \"*High Sierra*\", this driver can be unloaded this way:\n\n  .. code-block:: shell\n\n      sudo kextunload [-v] -bundle com.apple.driver.AppleUSBFTDI\n\n  You may want to use an alias or a tiny script such as\n  ``pyftdi/bin/uphy.sh``\n\n  Please note that the system automatically reloads the driver, so it may be\n  useful to move the kernel extension so that the system never loads it.\n\n  .. warning::\n\n     From macOS 10.14 \"*Mojave*\", the Apple kernel extension peacefully\n     co-exists with libusb_ and PyFtdi_, so you no longer need - and **should\n     not attempt** - to unload the kernel extension. If you still experience\n     this error, please verify you have not installed another driver from FTDI,\n     such as FTDI's D2XX.\n\n* On Linux: it may indicate a missing or invalid udev configuration. See\n  the :doc:`installation` section.\n\n* This error message may also be triggered whenever the communication port is\n  already in use.\n\n\n\"Error: The device has no langid\"\n`````````````````````````````````\n\n* On Linux, it usually comes from the same installation issue as the\n  ``Access denied`` error: the current user is not granted the permissions to\n  access the FTDI device, therefore pyusb cannot read the FTDI registers. Check\n  out the :doc:`installation` section.\n\n\n\"Bus error / Access violation\"\n``````````````````````````````\n\nPyFtdi does not use any native library, but relies on PyUSB_ and libusb_. The\nlatter uses native code that may trigger OS error. Some early development\nversions of libusb_, for example 1.0.22-b…, have been reported to trigger\nsuch issues. Please ensure you use a stable/final versions of libusb_ if you\nexperience this kind of fatal error.\n\n\n\"serial.serialutil.SerialException: Unable to open USB port\"\n````````````````````````````````````````````````````````````\n\nMay be caused by a conflict with the FTDI virtual COM port (VCOM). Try\nuninstalling the driver. On macOS, refer to this `FTDI macOS guide`_.\n\n\nSlow initialisation on OS X El Capitan\n``````````````````````````````````````\n\nIt may take several seconds to open or enumerate FTDI devices.\n\nIf you run libusb <= v1.0.20, be sure to read the `Libusb issue on macOS`_\nwith OS X 10.11+.\n\n"
  },
  {
    "path": "pyftdi/doc/urlscheme.rst",
    "content": ".. include:: defs.rst\n\n.. _url_scheme:\n\nURL Scheme\n----------\n\nThere are two ways to open a connection to an `Ftdi` object.\n\nThe recommended way to open a connection is to specify connection details\nusing a URL. The URL scheme is defined as:\n\n::\n\n    ftdi://[vendor][:[product][:serial|:bus:address|:index]]/interface\n\nwhere:\n\n* vendor: the USB vendor ID of the manufacturer\n\n  * ex: ``ftdi`` or ``0x403``\n\n* product: the USB product ID of the device\n\n  * ex: ``232h`` or ``0x6014``\n  * Supported product IDs: ``0x6001``, ``0x6010``, ``0x6011``, ``0x6014``,\n    ``0x6015``\n  * Supported product aliases:\n\n    * ``232``, ``232r``, ``232h``, ``2232d``, ``2232h``, ``4232h``, ``4232ha``,\n      ``230x``\n    * ``ft`` prefix for all aliases is also accepted, as for example ``ft232h``\n\n* ``serial``: the serial number as a string. This is the preferred method to\n  uniquely identify a specific FTDI device. However, some FTDI device are not\n  fitted with an EEPROM, or the EEPROM is either corrupted or erased. In this\n  case, FTDI devices report no serial number\n\n  Examples:\n     * ``ftdi://ftdi:232h:FT0FMF6V/1``\n     * ``ftdi://:232h:FT0FMF6V/1``\n     * ``ftdi://::FT0FMF6V/1``\n\n* ``bus:addess``: it is possible to select a FTDI device through a bus:address\n  pair, specified as *hexadecimal* integer values.\n\n  Examples:\n     * ``ftdi://ftdi:232h:10:22/1``\n     * ``ftdi://ftdi:232h:10:22/1``\n     * ``ftdi://::10:22/1``\n\n  Here, bus ``(0x)10`` = 16 (decimal) and address ``(0x)22`` = 34 (decimal)\n\n* ``index``: an integer - not particularly useful, as it depends on the\n  enumeration order on the USB buses, and may vary from on session to another.\n\n* ``interface``: the interface of FTDI device, starting from 1\n\n  * ``1`` for 230x and 232\\* devices,\n  * ``1`` or ``2`` for 2232\\* devices,\n  * ``1``, ``2``, ``3`` or ``4`` for 4232\\* devices\n\nAll parameters but the interface are optional, PyFtdi tries to find the best\nmatch. Therefore, if you have a single FTDI device connected to your system,\n``ftdi:///1`` should be enough.\n\nYou can also ask PyFtdi to enumerate all the compatible devices with the\nspecial ``ftdi:///?`` syntax. This syntax is useful to retrieve the available\nFTDI URLs with serial number and/or bus:address selectors. To avoid conflicts\nwith some shells such as `zsh`, escape the `?` char as ``ftdi:///\\?``.\n\nThere are several APIs available to enumerate/filter available FTDI device.\nSee :doc:`api/ftdi`.\n\nNote that opening an FTDI connection with a URL ending with `?` is interpreted\nas a query for matching FTDI devices and immediately stop. With this special\nURL syntax, the avaialble devices are printed out to the standard output, and\nthe Python interpreter is forced to exit (`SystemExit` is raised).\n\nWhen simple enumeration of the available FTDI devices is needed - so that\nexecution is not interrupted, two helper methods are available as\n:py:meth:`pyftdi.ftdi.Ftdi.list_devices` and\n:py:meth:`pyftdi.ftdi.Ftdi.show_devices` and accept the same URL syntax.\n\nOpening a connection\n~~~~~~~~~~~~~~~~~~~~\n\nURL-based methods to open a connection\n``````````````````````````````````````\n\n.. code-block:: python\n\n   open_from_url()\n   open_mpsse_from_url()\n   open_bitbang_from_url()\n\n\nDevice-based methods to open a connection\n`````````````````````````````````````````\n\nYou may also open an Ftdi device from an existing PyUSB_ device, with the help\nof the ``open_from_device()`` helper method.\n\n.. code-block:: python\n\n   open_from_device()\n   open_mpsse_from_device()\n   open_bitbang_from_device()\n\n\nLegacy methods to open a connection\n```````````````````````````````````\n\nThe old, deprecated method to open a connection is to use the ``open()``\nmethods without the ``_from_url`` suffix, which accept VID, PID, and serial\nparameters (among others).\n\n.. code-block:: python\n\n   open()\n   open_mpsse()\n   open_bitbang()\n\nSee the :ref:`ftdi_urls` tool to obtain the URLs for the connected FTDI\ndevices.\n"
  },
  {
    "path": "pyftdi/eeprom.py",
    "content": "# Copyright (c) 2019-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"EEPROM management for PyFdti\"\"\"\n\n# pylint: disable=wrong-import-position\n# pylint: disable=import-error\n\nimport sys\nfrom binascii import hexlify, unhexlify\nfrom collections import OrderedDict, namedtuple\nfrom configparser import ConfigParser\nfrom enum import IntEnum, IntFlag\nfrom logging import getLogger\nfrom random import randint\nfrom re import match\nfrom struct import calcsize as scalc, pack as spack, unpack as sunpack\nfrom typing import BinaryIO, List, Optional, Set, TextIO, Union, Tuple\nfrom usb.core import Device as UsbDevice\nfrom .ftdi import Ftdi, FtdiError\nfrom .misc import classproperty, to_bool, to_int\n\n\nclass FtdiEepromError(FtdiError):\n    \"\"\"FTDI EEPROM error.\"\"\"\n\n\nclass Hex2Int(int):\n    \"\"\"Hexa representation of a byte.\"\"\"\n    def __str__(self):\n        return f'0x{int(self):02x}'\n\n\nclass Hex4Int(int):\n    \"\"\"Hexa representation of a half-word.\"\"\"\n    def __str__(self):\n        return f'0x{int(self):04x}'\n\n\nclass FtdiEeprom:\n    \"\"\"FTDI EEPROM management\n    \"\"\"\n\n    _PROPS = namedtuple('PROPS', 'size user dynoff chipoff')\n    \"\"\"Properties for each FTDI device release.\n\n       * size is the size in bytes of the EEPROM storage area\n       * user is the size in bytes of the user storage area, if any/supported\n       * dynoff is the offset in EEPROM of the first bytes to store strings\n       * chipoff is the offset in EEPROM of the EEPROM chip type\n    \"\"\"\n\n    _PROPERTIES = {\n        0x0200: _PROPS(0, None, 0, None),        # FT232AM\n        0x0400: _PROPS(256, 0x14, 0x94, None),   # FT232BM\n        0x0500: _PROPS(256, 0x16, 0x96, 0x14),   # FT2232D\n        0x0600: _PROPS(128, None, 0x18, None),   # FT232R\n        0x0700: _PROPS(256, 0x1A, 0x9A, 0x18),   # FT2232H\n        0x0800: _PROPS(256, 0x1A, 0x9A, 0x18),   # FT4232H\n        0x0900: _PROPS(256, 0x1A, 0xA0, 0x1e),   # FT232H\n        0x1000: _PROPS(1024, 0x1A, 0xA0, None),  # FT230X/FT231X/FT234X\n        0x3600: _PROPS(256, 0x1A, 0x9A, 0x18),   # FT4232HA\n    }\n    \"\"\"EEPROM properties.\"\"\"\n\n    CBUS = IntEnum('CBus',\n                   'TXDEN PWREN TXLED RXLED TXRXLED SLEEP CLK48 CLK24 CLK12 '\n                   'CLK6 GPIO BB_WR BB_RD', start=0)\n    \"\"\"Alternate features for legacy FT232R devices.\"\"\"\n\n    CBUSH = IntEnum('CBusH',\n                    'TRISTATE TXLED RXLED TXRXLED PWREN SLEEP DRIVE0 DRIVE1 '\n                    'GPIO TXDEN CLK30 CLK15 CLK7_5', start=0)\n    \"\"\"Alternate features for FT232H/FT2232H/FT4232H devices.\"\"\"\n\n    CBUSX = IntEnum('CBusX',\n                    'TRISTATE TXLED RXLED TXRXLED PWREN SLEEP DRIVE0 DRIVE1 '\n                    'GPIO TXDEN CLK24 CLK12 CLK6 BAT_DETECT BAT_NDETECT '\n                    'I2C_TXE I2C_RXF VBUS_SENSE BB_WR BB_RD TIMESTAMP AWAKE',\n                    start=0)\n    \"\"\"Alternate features for FT230X devices.\"\"\"\n\n    UART_BITS = IntFlag('UartBits', 'TXD RXD RTS CTS DTR DSR DCD RI')\n    \"\"\"Inversion flags for FT232R and FT-X devices.\"\"\"\n\n    CHANNEL = IntFlag('Channel', 'FIFO OPTO CPU FT128 RS485')\n    \"\"\"Alternate port mode.\"\"\"\n\n    DRIVE = IntFlag('Drive',\n                    'LOW HIGH SLOW_SLEW SCHMITT _10 _20 _40 PWRSAVE_DIS')\n    \"\"\"Driver options for I/O pins.\"\"\"\n\n    CFG1 = IntFlag('Cfg1', 'CLK_IDLE_STATE DATA_LSB FLOW_CONTROL _08 '\n                           'HIGH_CURRENTDRIVE _20 _40 SUSPEND_DBUS7')\n    \"\"\"Configuration bits stored @ 0x01.\"\"\"\n\n    VAR_STRINGS = ('manufacturer', 'product', 'serial')\n    \"\"\"EEPROM strings with variable length.\"\"\"\n\n    def __init__(self):\n        self.log = getLogger('pyftdi.eeprom')\n        self._ftdi = Ftdi()\n        self._eeprom = bytearray()\n        self._size = 0\n        self._dev_ver = 0\n        self._valid = False\n        self._config = OrderedDict()\n        self._dirty = set()\n        self._modified = False\n        self._chip: Optional[int] = None\n        self._mirror = False\n        self._test_mode = False\n\n    def __getattr__(self, name):\n        if name in self._config:\n            return self._config[name]\n        raise AttributeError(f'No such attribute: {name}')\n\n    @classproperty\n    def eeprom_sizes(cls) -> List[int]:\n        # pylint: disable=no-self-argument\n        \"\"\"Return a list of supported EEPROM sizes.\n\n           :return: the supported EEPROM sizes\n        \"\"\"\n        return sorted({p.size for p in cls._PROPERTIES.values() if p.size})\n\n    def open(self, device: Union[str, UsbDevice],\n             ignore: bool = False, size: Optional[int] = None,\n             model: Optional[str] = None) -> None:\n        \"\"\"Open a new connection to the FTDI USB device.\n\n           :param device: the device URL or a USB device instance.\n           :param ignore: whether to ignore existing content\n           :param size: a custom EEPROM size\n           :param model: the EEPROM model used to specify a custom size\n        \"\"\"\n        if self._ftdi.is_connected:\n            raise FtdiError('Already open')\n        if isinstance(device, str):\n            self._ftdi.open_from_url(device)\n        else:\n            self._ftdi.open_from_device(device)\n        if model and not size:\n            # 93xxx46/56/66\n            mmo = match(r'(?i)^93[a-z]*([456])6.*$', model)\n            if not mmo:\n                raise ValueError(f'Unknown EEPROM device: {model}')\n            mmul = int(mmo.group(1))\n            size = 128 << (mmul - 4)\n        if size:\n            if size not in self.eeprom_sizes:\n                raise ValueError(f'Unsupported EEPROM size: {size}')\n            self._size = min(size, 256)\n        if not ignore:\n            self._eeprom = self._read_eeprom()\n            if self._valid:\n                self._decode_eeprom()\n\n    def close(self) -> None:\n        \"\"\"Close the current connection to the FTDI USB device,\n        \"\"\"\n        if self._ftdi.is_connected:\n            self._ftdi.close()\n            self._eeprom = bytearray()\n            self._dev_ver = 0\n            self._config.clear()\n\n    def connect(self, ftdi: Ftdi, ignore: bool = False) -> None:\n        \"\"\"Connect a FTDI EEPROM to an existing Ftdi instance.\n\n           :param ftdi: the Ftdi instance to use\n           :param ignore: whether to ignore existing content\n        \"\"\"\n        self._ftdi = ftdi\n        self._eeprom = bytearray()\n        self._dev_ver = 0\n        self._valid = False\n        self._config = OrderedDict()\n        self._dirty = set()\n        if not ignore:\n            self._eeprom = self._read_eeprom()\n            if self._valid:\n                self._decode_eeprom()\n            self._decode_eeprom()\n\n    @property\n    def device_version(self) -> int:\n        \"\"\"Report the version of the FTDI device.\n\n           :return: the release\n        \"\"\"\n        if not self._dev_ver:\n            if not self._ftdi.is_connected:\n                raise FtdiError('Not connected')\n            self._dev_ver = self._ftdi.device_version\n        return self._dev_ver\n\n    @property\n    def size(self) -> int:\n        \"\"\"Report the EEPROM size.\n\n           Use the most common (default) EEPROM size of the size is not yet\n           known.\n\n           :return: the size in bytes\n        \"\"\"\n        if not self._size:\n            self._size = self.default_size\n        return self._size\n\n    @property\n    def default_size(self) -> int:\n        \"\"\"Report the default EEPROM size based on the FTDI type.\n\n           The physical EEPROM size may be greater or lower, depending on the\n           actual connected EEPROM device.\n\n           :return: the size in bytes\n        \"\"\"\n        if self._chip == 0x46:\n            return 0x80  # 93C46\n        if self._chip == 0x56:\n            return 0x100  # 93C56\n        if self._chip == 0x66:\n            return 0x100  # 93C66 (512 bytes, only 256 are used)\n        try:\n            eeprom_size = self._PROPERTIES[self.device_version].size\n        except (AttributeError, KeyError) as exc:\n            raise FtdiError('No EEPROM') from exc\n        return eeprom_size\n\n    @property\n    def storage_size(self) -> int:\n        \"\"\"Report the number of EEPROM bytes that can be used for configuration\n            storage. The physical EEPROM size may be greater\n\n            :return: the number of bytes in the eeprom that will be used for\n                configuration storage\n        \"\"\"\n        try:\n            eeprom_storage_size = self.size\n            if self.is_mirroring_enabled:\n                eeprom_storage_size = self.mirror_sector\n        except FtdiError as exc:\n            raise exc\n        return eeprom_storage_size\n\n    @property\n    def data(self) -> bytes:\n        \"\"\"Returns the content of the EEPROM.\n\n           :return: the content as bytes.\n        \"\"\"\n        self._sync_eeprom()\n        return bytes(self._eeprom)\n\n    @property\n    def properties(self) -> Set[str]:\n        \"\"\"Returns the supported properties for the current device.\n\n           :return: the supported properies.\n        \"\"\"\n        props = set(self._config.keys())\n        props -= set(self.VAR_STRINGS)\n        return props\n\n    @property\n    def is_empty(self) -> bool:\n        \"\"\"Reports whether the EEPROM has been erased, or no EEPROM is\n           connected to the FTDI EEPROM port.\n\n           :return: True if no content is detected\n        \"\"\"\n        if len(self._eeprom) != self.size:\n            return False\n        for byte in self._eeprom:\n            if byte != 0xFF:\n                return False\n        return True\n\n    @property\n    def cbus_pins(self) -> List[int]:\n        \"\"\"Return the list of CBUS pins configured as GPIO, if any\n\n           :return: list of CBUS pins\n        \"\"\"\n        pins = [pin for pin in range(0, 10)\n                if self._config.get(f'cbus_func_{pin}', '') == 'GPIO']\n        return pins\n\n    @property\n    def cbus_mask(self) -> int:\n        \"\"\"Return the bitmask of CBUS pins configured as GPIO.\n\n           The bitmap contains four bits, ordered in natural order.\n\n           :return: CBUS mask\n        \"\"\"\n        if self.device_version == 0x900:  # FT232H\n            cbus = [5, 6, 8, 9]\n        else:\n            cbus = list(range(4))\n        mask = 0\n        for bix, pin in enumerate(cbus):\n            if self._config.get(f'cbus_func_{pin}', '') == 'GPIO':\n                mask |= 1 << bix\n        return mask\n\n    @property\n    def has_mirroring(self) -> bool:\n        \"\"\"Report whether the device supports EEPROM content duplication\n           across its two sectors.\n\n           :return: True if the device support mirorring\n        \"\"\"\n        return (self._PROPERTIES[self.device_version].user and\n                self._ftdi.device_version != 0x1000)\n\n    @property\n    def mirror_sector(self) -> int:\n        \"\"\"Report start address of the mirror sector in the EEPROM.\n           This is only valid if the FTDI is capable of mirroring EEPROM data.\n\n           :return: the start address\n        \"\"\"\n        if self.has_mirroring:\n            return self.size // 2\n        raise FtdiError('EEPROM does not support mirroring')\n\n    @property\n    def is_mirroring_enabled(self) -> bool:\n        \"\"\"Check if EEPROM mirroring is currently enabled for this EEPROM.\n            See enable_mirroring for more details on EEPROM mirroring\n            functionality\n        \"\"\"\n        return self.has_mirroring and self._mirror\n\n    def enable_mirroring(self, enable: bool) -> None:\n        \"\"\"Enable EEPROM write mirroring. When enabled, this divides the EEPROM\n           into 2 sectors and mirrors configuration data between them.\n\n           For example on a 256 byte EEPROM, two 128 byte 'sectors' will be\n           used to store identical data. Configuration properties/strings will\n           be writen to both of these sectors. For some devices (like the\n           4232H), this makes the PyFtdi EEPROM functionally similar to\n           FT_PROG.\n\n           Note: Data will only be mirrored if the has_mirroring property\n           returns true (after establishing a connection to the ftdi)\n\n           :param enable: enable or disable EEPROM mirroring\n        \"\"\"\n        self._mirror = enable\n\n    def save_config(self, file: TextIO) -> None:\n        \"\"\"Save the EEPROM content as an INI stream.\n\n           :param file: output stream\n        \"\"\"\n        self._sync_eeprom()\n        cfg = ConfigParser()\n        cfg.add_section('values')\n        for name, value in self._config.items():\n            val = str(value)\n            if isinstance(value, bool):\n                val = val.lower()\n            cfg.set('values', name, val)\n        cfg.add_section('raw')\n        length = 16\n        for i in range(0, len(self._eeprom), length):\n            chunk = self._eeprom[i:i+length]\n            hexa = hexlify(chunk).decode()\n            cfg.set('raw', f'@{i:02x}', hexa)\n        cfg.write(file)\n\n    def load_config(self, file: TextIO, section: Optional[str] = None) -> None:\n        \"\"\"Load the EEPROM content from an INI stream.\n\n           The ``section`` argument selects which section(s) to load:\n\n           * ``raw`` only loads the raw data (hexabytes) from a previous dump\n           * ``values`` only loads the values section, that is the human\n             readable configuration.\n           * ``all``, which is the default section selection, load the raw\n             section, then overwrite part of it with any configuration value\n             from the ``values`` section. This provides a handy way to use an\n             existing dump from a valid EEPROM content, while customizing some\n             parameters, such as the serial number.\n\n           :param file: input stream\n           :paran section: which section to load from the ini file\n        \"\"\"\n        self._sync_eeprom()\n        cfg = ConfigParser()\n        cfg.read_file(file)\n        loaded = False\n        sections = cfg.sections()\n        if section not in ('all', None) and section not in sections:\n            raise FtdiEepromError(f'No such configuration section {section}')\n        sect = 'raw'\n        if sect in sections and section in (None, 'all', sect):\n            if not cfg.has_section(sect):\n                raise FtdiEepromError(f\"No '{sect}' section in INI file\")\n            options = cfg.options(sect)\n            try:\n                for opt in options:\n                    if not opt.startswith('@'):\n                        raise ValueError()\n                    address = int(opt[1:], 16)\n                    hexval = cfg.get(sect, opt).strip()\n                    buf = unhexlify(hexval)\n                    self._eeprom[address:address+len(buf)] = buf\n            except IndexError as exc:\n                raise ValueError(f\"Invalid address in '{sect}' \"\n                                 f\"section\") from exc\n            except ValueError as exc:\n                raise ValueError(f\"Invalid line in '{sect}' section\") from exc\n            self._compute_crc(self._eeprom, True)\n            if not self._valid:\n                raise ValueError('Loaded RAW section is invalid (CRC mismatch')\n            loaded = True\n        sect = 'values'\n        vmap = {\n            'manufacturer': 'manufacturer_name',\n            'product': 'product_name',\n            'serial': 'serial_number'\n        }\n        if sect in sections and section in (None, 'all', sect):\n            if not cfg.has_section(sect):\n                raise FtdiEepromError(f\"No '{sect}' section in INI file\")\n            options = cfg.options(sect)\n            for opt in options:\n                value = cfg.get(sect, opt).strip()\n                if opt in vmap:\n                    func = getattr(self, f'set_{vmap[opt]}')\n                    func(value)\n                else:\n                    self.log.debug('Assigning opt %s = %s', opt, value)\n                    try:\n                        self.set_property(opt, value)\n                    except (TypeError, ValueError, NotImplementedError) as exc:\n                        self.log.warning(\"Ignoring setting '%s': %s\", opt, exc)\n            loaded = True\n        if not loaded:\n            raise ValueError(f'Invalid section: {section}')\n        self._sync_eeprom()\n\n    def set_serial_number(self, serial: str) -> None:\n        \"\"\"Define a new serial number.\"\"\"\n        self._validate_string(serial)\n        self._update_var_string('serial', serial)\n        self.set_property('has_serial', True)\n\n    def set_manufacturer_name(self, manufacturer: str) -> None:\n        \"\"\"Define a new manufacturer string.\"\"\"\n        self._validate_string(manufacturer)\n        self._update_var_string('manufacturer', manufacturer)\n\n    def set_product_name(self, product: str) -> None:\n        \"\"\"Define a new product name.\"\"\"\n        self._validate_string(product)\n        self._update_var_string('product', product)\n\n    def set_property(self, name: str, value: Union[str, int, bool],\n                     out: Optional[TextIO] = None) -> None:\n        \"\"\"Change the value of a stored property.\n\n           :see: :py:meth:`properties` for a list of valid property names.\n                 Note that for now, only a small subset of properties can be\n                 changed.\n           :param name: the property to change\n           :param value: the new value (supported values depend on property)\n           :param out: optional output stream to report hints\n        \"\"\"\n        mobj = match(r'cbus_func_(\\d)', name)\n        if mobj:\n            if not isinstance(value, str):\n                raise ValueError(\"'{name}' should be specified as a string\")\n            self._set_cbus_func(int(mobj.group(1)), value, out)\n            self._dirty.add(name)\n            return\n        mobj = match(r'([abcd])bus_(drive|slow_slew|schmitt)', name)\n        if mobj:\n            self._set_bus_control(mobj.group(1), mobj.group(2), value, out)\n            self._dirty.add(name)\n            return\n        mobj = match(r'group_(\\d)_(drive|schmitt|slow_slew)', name)\n        if mobj:\n            self._set_group(int(mobj.group(1)), mobj.group(2), value, out)\n            self._dirty.add(name)\n            return\n        mobj = match(r'channel_([abcd])_type', name)\n        if mobj:\n            chn = mobj.group(1)\n            if value == 'UART':\n                val = 0\n            else:\n                val = self.CHANNEL[value]\n            if self.device_version == 0x0700 and chn in 'ab':\n                # FT2232H\n                idx = 0x00 if chn == 'a' else 0x01\n                mask = 0x07\n            elif self.device_version == 0x0800:\n                # FT4232H\n                idx = 0x0b\n                mask = 1 << {'a': 4, 'b': 5, 'c': 6, 'd': 7}.get(chn)\n                val = mask if val > 0 else 0\n            elif self.device_version == 0x0900 and chn == 'a':\n                # FT232H\n                idx = 0x00\n                mask = 0x0F\n            else:\n                raise ValueError(\n                    f\"Option '{name}' not supported by the device\")\n            if val & ~mask:\n                raise ValueError(\n                    f\"Unsupported value for setting '{name}': {val}\")\n            self._eeprom[idx] &= ~mask\n            self._eeprom[idx] |= val\n            if self.is_mirroring_enabled:\n                idx2 = self.mirror_sector + idx\n                self._eeprom[idx2] &= ~mask\n                self._eeprom[idx2] |= val\n            self._dirty.add(name)\n            return\n        mobj = match(r'channel_([abcd])_driver', name)\n        if mobj:\n            chn = mobj.group(1)\n            if value == 'VCP':\n                val = 1\n            elif value == 'D2XX':\n                val = 0\n            else:\n                raise ValueError(\n                    f\"Invalid value '{value} for '{name}'\")\n            if self.device_version == 0x0700 and chn in 'ab':\n                # FT2232H\n                idx = 0x00 if chn == 'a' else 0x01\n                mask = 1 << 3\n            elif self.device_version == 0x0800:\n                # FT4232H\n                idx = {'a': 0, 'b': 1, 'c': 0, 'd': 1}.get(chn)\n                mask = 1 << {'a': 3, 'b': 3, 'c': 7, 'd': 7}.get(chn)\n            elif self.device_version == 0x0900 and chn == 'a':\n                # FT232H\n                idx = 0x00\n                mask = 1 << 4\n            else:\n                raise ValueError(\n                    f\"Option '{name}' not supported by the device\")\n            self._eeprom[idx] &= ~mask\n            if val:\n                self._eeprom[idx] |= mask\n            if self.is_mirroring_enabled:\n                idx2 = self.mirror_sector + idx\n                self._eeprom[idx2] &= ~mask\n                if val:\n                    self._eeprom[idx2] |= mask\n            self._dirty.add(name)\n            return\n        confs = {\n            'remote_wakeup': (0, 5),\n            'self_powered': (0, 6),\n            'in_isochronous': (2, 0),\n            'out_isochronous': (2, 1),\n            'suspend_pull_down': (2, 2),\n            'has_serial': (2, 3),\n        }\n        hwords = {\n            'vendor_id': 0x02,\n            'product_id': 0x04,\n            'type': 0x06,\n        }\n        if self.device_version in (0x0400, 0x0500):\n            # Type BM and 2232C/D use 0xc to encode the USB version to expose\n            # H device use this location to encode bus/group properties\n            hwords['usb_version'] = 0x0c\n            confs['use_usb_version'] = (2, 4)\n        if name in hwords:\n            val = to_int(value)\n            if not 0 <= val <= 0xFFFF:\n                raise ValueError(f'Invalid value for {name}')\n            offset = hwords[name]\n            self._eeprom[offset:offset+2] = spack('<H', val)\n            if self.is_mirroring_enabled:\n                # duplicate in 'sector 2'\n                offset2 = self.mirror_sector + offset\n                self._eeprom[offset2:offset2+2] = spack('<H', val)\n            self._dirty.add(name)\n            return\n        if name in confs:\n            val = to_bool(value, permissive=False, allow_int=True)\n            offset, bit = confs[name]\n            mask = 1 << bit\n            idx = 0x08 + offset\n            if val:\n                self._eeprom[idx] |= mask\n                if self.is_mirroring_enabled:\n                    # duplicate in 'sector 2'\n                    idx2 = self.mirror_sector + idx\n                    self._eeprom[idx2] |= mask\n            else:\n                self._eeprom[idx] &= ~mask\n                if self.is_mirroring_enabled:\n                    # duplicate in 'sector 2'\n                    idx2 = self.mirror_sector + idx\n                    self._eeprom[idx2] &= ~mask\n            self._dirty.add(name)\n            return\n        if name == 'power_max':\n            val = to_int(value) >> 1\n            idx = 0x09\n            self._eeprom[idx] = val\n            if self.is_mirroring_enabled:\n                # duplicate in 'sector 2'\n                idx2 = self.mirror_sector + idx\n                self._eeprom[idx2] = val\n            self._dirty.add(name)\n            return\n        if name.startswith('invert_'):\n            if self.device_version not in (0x600, 0x1000):\n                raise ValueError('UART control line inversion not available '\n                                 'with this device')\n            self._set_invert(name[len('invert_'):], value, out)\n            self._dirty.add(name)\n            return\n        if name == 'chip':\n            val = to_int(value)\n            idx = self._PROPERTIES[self.device_version].chipoff\n            if idx is None:\n                raise ValueError(\n                    f\"Setting '{name}' is not supported by the chip\")\n            self._eeprom[idx] = val\n            if self.is_mirroring_enabled:\n                idx2 = self.mirror_sector + idx\n                self._eeprom[idx2] = val\n            self._dirty.add(name)\n            return\n        if name == 'suspend_dbus7':\n            val = to_bool(value, permissive=False, allow_int=True)\n            if self.device_version == 0x0700:\n                # FT2232H\n                idx = 0x01\n                mask = self.CFG1.SUSPEND_DBUS7.value\n                self._eeprom[idx] &= ~mask\n                if val:\n                    self._eeprom[idx] |= mask\n                if self.is_mirroring_enabled:\n                    idx2 = self.mirror_sector + idx\n                    self._eeprom[idx2] &= ~mask\n                    if val:\n                        self._eeprom[idx2] |= mask\n            else:\n                raise ValueError(\n                    f\"Setting '{name}' is not supported by the chip\")\n            self._dirty.add(name)\n            return\n        if name in self.properties:\n            if name not in self._config:\n                raise NotImplementedError('Change is not supported')\n            curval = self._config[name]\n            try:\n                curtype = type(curval)\n                value = curtype(value)\n            except (ValueError, TypeError) as exc:\n                raise ValueError(f\"Cannot be converted to the proper type \"\n                                 f\"'{curtype}'\") from exc\n            if value != curval:\n                raise NotImplementedError('Not yet supported')\n            # no-op change is silently ignored\n            return\n        raise ValueError(f'Unknown property: {name}')\n\n    def erase(self, erase_byte: Optional[int] = 0xFF) -> None:\n        \"\"\"Erase the whole EEPROM.\n\n            :param erase_byte: Optional erase byte to use. Default to 0xFF\n        \"\"\"\n        self._eeprom = bytearray([erase_byte] * self.size)\n        self._config.clear()\n        self._dirty.add('eeprom')\n\n    def initialize(self) -> None:\n        \"\"\"Initialize the EEPROM with some default sensible values.\n        \"\"\"\n        dev_ver = self.device_version\n        dev_name = Ftdi.DEVICE_NAMES[dev_ver]\n        vid = Ftdi.FTDI_VENDOR\n        pid = Ftdi.PRODUCT_IDS[vid][dev_name]\n        self.set_manufacturer_name('FTDI')\n        self.set_product_name(dev_name.upper())\n        sernum = ''.join([chr(randint(ord('A'), ord('Z'))) for _ in range(5)])\n        self.set_serial_number(f'FT{randint(0, 9)}{sernum}')\n        self.set_property('vendor_id', vid)\n        self.set_property('product_id', pid)\n        self.set_property('type', dev_ver)\n        self.set_property('power_max', 150)\n        self._sync_eeprom()\n\n    def sync(self) -> None:\n        \"\"\"Force re-evaluation of configuration after some changes.\n\n           This API is not useful for regular usage, but might help for testing\n           when the EEPROM does not go through a full save/load cycle\n        \"\"\"\n        self._sync_eeprom()\n\n    def dump_config(self, file: Optional[BinaryIO] = None) -> None:\n        \"\"\"Dump the configuration to a file.\n\n           :param file: the output file, default to stdout\n        \"\"\"\n        if self._dirty:\n            self._decode_eeprom()\n        for name, value in self._config.items():\n            print(f'{name}: {value}', file=file or sys.stdout)\n\n    def commit(self, dry_run: bool = True, no_crc: bool = False) -> bool:\n        \"\"\"Commit any changes to the EEPROM.\n\n           :param dry_run: log what should be written, do not actually change\n                  the EEPROM content\n           :param no_crc: do not compute EEPROM CRC. This should only be used\n            to perform a full erasure of the EEPROM, as an attempt to recover\n            from a corrupted config.\n\n           :return: True if some changes have been committed to the EEPROM\n        \"\"\"\n        self._sync_eeprom(no_crc)\n        if not self._modified:\n            self.log.warning('No change to commit, EEPROM not modified')\n            return False\n        self._ftdi.overwrite_eeprom(self._eeprom, dry_run=dry_run)\n        if not dry_run:\n            eeprom = self._read_eeprom()\n            if eeprom != self._eeprom:\n                pos = 0\n                for pos, (old, new) in enumerate(zip(self._eeprom, eeprom)):\n                    if old != new:\n                        break\n                pos &= ~0x1\n                raise FtdiEepromError(f'Write to EEPROM failed @ 0x{pos:02x}')\n            self._modified = False\n        return dry_run\n\n    def reset_device(self):\n        \"\"\"Execute a USB device reset.\"\"\"\n        self._ftdi.reset(usb_reset=True)\n\n    def set_test_mode(self, enable: bool):\n        \"\"\"Enable test mode (silence some warnings).\"\"\"\n        self._test_mode = enable\n\n    @classmethod\n    def _validate_string(cls, string):\n        for invchr in ':/':\n            # do not accept characters which are interpreted as URL seperators\n            if invchr in string:\n                raise ValueError(f\"Invalid character '{invchr}' in string\")\n\n    def _update_var_string(self, name: str, value: str) -> None:\n        if name not in self.VAR_STRINGS:\n            raise ValueError(f'{name} is not a variable string')\n        try:\n            if value == self._config[name]:\n                return\n        except KeyError:\n            # not yet defined\n            pass\n        self._config[name] = value\n        self._dirty.add(name)\n\n    def _generate_var_strings(self, fill=True) -> None:\n        \"\"\"\n            :param fill: fill the remainder of the space after the var strings\n                with 0s\n        \"\"\"\n        stream = bytearray()\n        dynpos = self._PROPERTIES[self.device_version].dynoff\n        if dynpos > self._size:\n            # if a custom, small EEPROM device is used\n            dynpos = 0x40\n        data_pos = dynpos\n        tbl_pos = 0x0e\n        if self.is_mirroring_enabled:\n            # start of var-strings in sector 1 (used for mirrored config)\n            s1_vstr_start = data_pos - self.mirror_sector\n            tbl_sector2_pos = self.mirror_sector + tbl_pos\n        for name in self.VAR_STRINGS:\n            try:\n                ustr = self._config[name].encode('utf-16le')\n            except KeyError:\n                ustr = ''\n            length = len(ustr)+2\n            stream.append(length)\n            stream.append(0x03)  # string descriptor\n            stream.extend(ustr)\n            self._eeprom[tbl_pos] = data_pos | 0x80\n            tbl_pos += 1\n            if self.is_mirroring_enabled:\n                self._eeprom[tbl_sector2_pos] = data_pos\n                tbl_sector2_pos += 1\n            self._eeprom[tbl_pos] = length\n            tbl_pos += 1\n            if self.is_mirroring_enabled:\n                self._eeprom[tbl_sector2_pos] = length\n                tbl_sector2_pos += 1\n            data_pos += length\n        if self.is_mirroring_enabled:\n            self._eeprom[s1_vstr_start:s1_vstr_start+len(stream)] = stream\n        self._eeprom[dynpos:dynpos+len(stream)] = stream\n        mtp = self._ftdi.device_version == 0x1000\n        crc_pos = 0x100 if mtp else self._size\n        rem = crc_pos - (dynpos + len(stream))\n        if rem < 0:\n            oversize = (-rem + 2) // 2\n            raise FtdiEepromError(f'Cannot fit strings into EEPROM, '\n                                  f'{oversize} oversize characters')\n        if fill:\n            self._eeprom[dynpos+len(stream):crc_pos] = bytes(rem)\n            if self.is_mirroring_enabled:\n                crc_s1_pos = self.mirror_sector\n                self._eeprom[s1_vstr_start+len(stream):crc_s1_pos] = bytes(rem)\n\n    def _sync_eeprom(self, no_crc: bool = False):\n        if not self._dirty:\n            self.log.debug('No change detected for EEPROM content')\n            return\n        if not no_crc:\n            if any(x in self._dirty for x in self.VAR_STRINGS):\n                self._generate_var_strings()\n                for varstr in self.VAR_STRINGS:\n                    self._dirty.discard(varstr)\n            self._update_crc()\n            self._decode_eeprom()\n        self._dirty.clear()\n        self._modified = True\n        self.log.debug('EEPROM content regenerated (not yet committed)')\n\n    def _compute_crc(self, eeprom: Union[bytes, bytearray], check=False):\n        mtp = self._ftdi.device_version == 0x1000\n        crc_pos = 0x100 if mtp else len(eeprom)\n        crc_size = scalc('<H')\n        if not check:\n            # check mode: add CRC itself, so that result should be zero\n            crc_pos -= crc_size\n        if self.is_mirroring_enabled:\n            mirror_s1_crc_pos = self.mirror_sector\n            if not check:\n                mirror_s1_crc_pos -= crc_size\n            # if mirroring, only calculate the crc for the first sector/half\n            #   of the eeprom. Data (including this crc) are duplicated in\n            #   the second sector/half\n            crc = self._ftdi.calc_eeprom_checksum(eeprom[:mirror_s1_crc_pos])\n        else:\n            crc = self._ftdi.calc_eeprom_checksum(eeprom[:crc_pos])\n        if check:\n            self._valid = not bool(crc)\n            if not self._valid:\n                self.log.debug('CRC is now 0x%04x', crc)\n            else:\n                self.log.debug('CRC OK')\n        return crc, crc_pos, crc_size\n\n    def _update_crc(self):\n        crc, crc_pos, crc_size = self._compute_crc(\n            self._eeprom, False)\n        self._eeprom[crc_pos:crc_pos+crc_size] = spack('<H', crc)\n        if self.is_mirroring_enabled:\n            # if mirroring calculate where the CRC will start in first sector\n            crc_s1_start = self.mirror_sector - crc_size\n            self._eeprom[crc_s1_start:crc_s1_start+crc_size] = spack('<H', crc)\n\n    def _compute_size(self, eeprom: Union[bytes, bytearray]) \\\n            -> Tuple[int, bool]:\n        \"\"\"\n            :return: Tuple of:\n                - int of usable size of the eeprom\n                - bool of whether eeprom mirroring was detected or not\n        \"\"\"\n        if self._ftdi.is_eeprom_internal:\n            return self._ftdi.max_eeprom_size, False\n        if all(x == 0xFF for x in eeprom):\n            # erased EEPROM, size is unknown\n            return self._ftdi.max_eeprom_size, False\n        if eeprom[0:0x80] == eeprom[0x80:0x100]:\n            return 0x80, True\n        if eeprom[0:0x40] == eeprom[0x40:0x80]:\n            return 0x40, True\n        return 0x100, False\n\n    def _read_eeprom(self) -> bytes:\n        buf = self._ftdi.read_eeprom(0, eeprom_size=self.size)\n        eeprom = bytearray(buf)\n        size, mirror_detected = self._compute_size(eeprom)\n        if size < len(eeprom):\n            eeprom = eeprom[:size]\n        crc = self._compute_crc(eeprom, True)[0]\n        if crc:\n            if self.is_empty:\n                self.log.info('No EEPROM or EEPROM erased')\n            else:\n                self.log.error('Invalid CRC or EEPROM content')\n        if not self.is_empty and mirror_detected:\n            self.log.info('Detected a mirrored eeprom. '\n                          'Enabling mirrored writing')\n            self._mirror = True\n        return eeprom\n\n    def _decode_eeprom(self):\n        cfg = self._config\n        cfg.clear()\n        chipoff = self._PROPERTIES[self.device_version].chipoff\n        if chipoff is not None:\n            self._chip = Hex2Int(self._eeprom[chipoff])\n            cfg['chip'] = self._chip\n        cfg['vendor_id'] = Hex4Int(sunpack('<H', self._eeprom[0x02:0x04])[0])\n        cfg['product_id'] = Hex4Int(sunpack('<H', self._eeprom[0x04:0x06])[0])\n        cfg['type'] = Hex4Int(sunpack('<H', self._eeprom[0x06:0x08])[0])\n        power_supply, power_max, conf = sunpack('<3B', self._eeprom[0x08:0x0b])\n        cfg['self_powered'] = bool(power_supply & (1 << 6))\n        cfg['remote_wakeup'] = bool(power_supply & (1 << 5))\n        cfg['power_max'] = power_max << 1\n        cfg['has_serial'] = bool(conf & (1 << 3))\n        cfg['suspend_pull_down'] = bool(conf & (1 << 2))\n        cfg['out_isochronous'] = bool(conf & (1 << 1))\n        cfg['in_isochronous'] = bool(conf & (1 << 0))\n        cfg['manufacturer'] = self._decode_string(0x0e)\n        cfg['product'] = self._decode_string(0x10)\n        cfg['serial'] = self._decode_string(0x12)\n        if self.device_version in (0x0400, 0x0500):\n            cfg['use_usb_version'] = bool(conf & (1 << 3))\n            if cfg['use_usb_version']:\n                cfg['usb_version'] = \\\n                    Hex4Int(sunpack('<H', self._eeprom[0x0c:0x0e])[0])\n        if cfg['type'] == 0xffff:\n            if not self._test_mode:\n                self.log.warning('EEPROM type is erased')\n            return\n        name = None\n        try:\n            type_ = cfg['type']\n            if type_ == 0:\n                type_ = self.device_version\n            name = Ftdi.DEVICE_NAMES[type_].replace('-', '')\n            if name.startswith('ft'):\n                name = name[2:]\n            func = getattr(self, f'_decode_{name}')\n        except (KeyError, AttributeError):\n            self.log.warning('No EEPROM decoder for device %s', name or '?')\n        else:\n            func()\n\n    def _decode_string(self, offset):\n        str_offset, str_size = sunpack('<BB', self._eeprom[offset:offset+2])\n        if str_size:\n            str_offset &= self.size - 1\n            str_size -= scalc('<H')\n            str_offset += scalc('<H')\n            manufacturer = self._eeprom[str_offset:str_offset+str_size]\n            return manufacturer.decode('utf16', errors='ignore')\n        return ''\n\n    def _set_cbus_func(self, cpin: int, value: str,\n                       out: Optional[TextIO]) -> None:\n        cmap = {0x600: (self.CBUS, 5, 0x14, 4),    # FT232R\n                0x900: (self.CBUSH, 10, 0x18, 4),  # FT232H\n                0x1000: (self.CBUSX, 4, 0x1A, 8)}  # FT230X/FT231X/FT234X\n        try:\n            cbus, count, offset, width = cmap[self.device_version]\n        except KeyError as exc:\n            raise ValueError('This property is not supported on this '\n                             'device') from exc\n        pin_filter = getattr(self,\n                             f'_filter_cbus_func_x{self.device_version:x}',\n                             None)\n        if value == '?' and out:\n            items = {item.name for item in cbus}\n            if pin_filter:\n                items = {val for val in items if pin_filter(cpin, val)}\n            print(', '.join(sorted(items)) if items else '(none)', file=out)\n            return\n        if not 0 <= cpin < count:\n            raise ValueError(f\"Unsupported CBUS pin '{cpin}'\")\n        try:\n            code = cbus[value.upper()].value\n        except KeyError as exc:\n            raise ValueError(f\"CBUS pin '{cpin}' does not have function \"\n                             f\"{value}'\") from exc\n        if pin_filter and not pin_filter(cpin, value.upper()):\n            raise ValueError(f\"Unsupported CBUS function '{value}' for pin \"\n                             f\"'{cpin}'\")\n        addr = offset + (cpin*width)//8\n        if width == 4:\n            bitoff = 4 if cpin & 0x1 else 0\n            mask = 0x0F << bitoff\n        else:\n            bitoff = 0\n            mask = 0xFF\n        old = self._eeprom[addr]\n        self._eeprom[addr] &= ~mask\n        self._eeprom[addr] |= code << bitoff\n        self.log.debug('Cpin %d, addr 0x%02x, value 0x%02x->0x%02x',\n                       cpin, addr, old, self._eeprom[addr])\n\n    @classmethod\n    def _filter_cbus_func_x900(cls, cpin: int, value: str):\n        if cpin == 7:\n            # nothing can be assigned to ACBUS7\n            return False\n        if value in 'TRISTATE TXLED RXLED TXRXLED PWREN SLEEP DRIVE0'.split():\n            # any pin can be assigned these functions\n            return True\n        if cpin in (5, 6, 8, 9):\n            # any function can be assigned to ACBUS5, ACBUS6, ACBUS8, ACBUS9\n            return True\n        if cpin == 0:\n            return value != 'GPIO'\n        return False\n\n    @classmethod\n    def _filter_cbus_func_x600(cls, cpin: int, value: str):\n        if value == 'BB_WR':\n            # this signal is only available on CBUS0, CBUS1\n            return cpin < 2\n        return True\n\n    def _set_bus_control(self, bus: str, control: str,\n                         value: Union[str, int, bool],\n                         out: Optional[TextIO]) -> None:\n        if self.device_version == 0x1000:\n            self._set_bus_control_230x(bus, control, value, out)\n            return\n        # for now, only support FT-X devices\n        raise ValueError('Bus control not implemented for this device')\n\n    def _set_group(self, group: int, control: str,\n                   value: Union[str, int, bool], out: Optional[TextIO]) \\\n            -> None:\n        if self.device_version in (0x0700, 0x0800, 0x0900, 0x3600):\n            self._set_group_x232h(group, control, value, out)\n            return\n        raise ValueError('Group not implemented for this device')\n\n    def _set_bus_control_230x(self, bus: str, control: str,\n                              value: Union[str, int, bool],\n                              out: Optional[TextIO]) -> None:\n        if bus not in 'cd':\n            raise ValueError(f'Invalid bus: {bus}')\n        self._set_bus_xprop(0x0c, bus == 'c', control, value, out)\n\n    def _set_group_x232h(self, group: int, control: str, value: str,\n                         out: Optional[TextIO]) -> None:\n        # 2232H/4232H/4232HA\n        if self.device_version in (0x0700, 0x800, 0x3600):\n            offset = 0x0c + group//2\n            nibble = group & 1\n        else:  # 232H\n            offset = 0x0c + group\n            nibble = 0\n        self._set_bus_xprop(offset, nibble, control, value, out)\n\n    def _set_bus_xprop(self, offset: int, high_nibble: bool, control: str,\n                       value: Union[str, int, bool], out: Optional[TextIO]) \\\n            -> None:\n        try:\n            if control == 'drive':\n                candidates = (4, 8, 12, 16)\n                if value == '?' and out:\n                    print(', '.join([str(v) for v in candidates]), file=out)\n                    return\n                value = int(value)\n                if value not in candidates:\n                    raise ValueError(f'Invalid drive current: {value} mA')\n                value //= 4\n                value -= 1\n            elif control in ('slow_slew', 'schmitt'):\n                if value == '?' and out:\n                    print('off, on', file=out)\n                    return\n                value = int(to_bool(value))\n            else:\n                raise ValueError(f'Unsupported control: {control}')\n        except (ValueError, TypeError) as exc:\n            raise ValueError(f'Invalid {control} value: {value}') from exc\n        config = self._eeprom[offset]\n        if not high_nibble:\n            conf = config & 0x0F\n            config &= 0xF0\n            cshift = 0\n        else:\n            conf = config >> 4\n            config &= 0x0F\n            cshift = 4\n        if control == 'drive':\n            conf &= 0b1100\n            conf |= value\n        elif control == 'slow_slew':\n            conf &= 0b1011\n            conf |= value << 2\n        elif control == 'schmitt':\n            conf &= 0b0111\n            conf |= value << 3\n        else:\n            raise RuntimeError('Internal error')\n        config |= conf << cshift\n        self._eeprom[offset] = config\n\n    def _set_invert(self, name, value, out):\n        if value == '?' and out:\n            print('off, on', file=out)\n            return\n        if name.upper() not in self.UART_BITS.__members__:\n            raise ValueError(f'Unknown property: {name}')\n        value = to_bool(value, permissive=False)\n        code = getattr(self.UART_BITS, name.upper())\n        invert = self._eeprom[0x0B]\n        if value:\n            invert |= code\n        else:\n            invert &= ~code\n        self._eeprom[0x0B] = invert\n\n    def _decode_x(self):\n        # FT-X series\n        cfg = self._config\n        misc, = sunpack('<H', self._eeprom[0x00:0x02])\n        cfg['channel_a_driver'] = 'VCP' if misc & (1 << 7) else 'D2XX'\n        for bit in self.UART_BITS:\n            value = self._eeprom[0x0B]\n            cfg[f'invert_{self.UART_BITS(bit).name}'] = bool(value & bit)\n        max_drive = self.DRIVE.LOW.value | self.DRIVE.HIGH.value\n        value = self._eeprom[0x0c]\n        for grp in range(2):\n            conf = value & 0xF\n            bus = 'c' if grp else 'd'\n            cfg[f'{bus}bus_drive'] = 4 * (1+(conf & max_drive))\n            cfg[f'{bus}bus_schmitt'] = bool(conf & self.DRIVE.SCHMITT)\n            cfg[f'{bus}bus_slow_slew'] = bool(conf & self.DRIVE.SLOW_SLEW)\n            value >>= 4\n        for bix in range(4):\n            value = self._eeprom[0x1A + bix]\n            try:\n                cfg[f'cbus_func_{bix}'] = self.CBUSX(value).name\n            except ValueError:\n                pass\n\n    def _decode_232h(self):\n        cfg = self._config\n        cfg0, cfg1 = self._eeprom[0x00], self._eeprom[0x01]\n        cfg['channel_a_type'] = cfg0 & 0x0F\n        cfg['channel_a_driver'] = 'VCP' if (cfg0 & (1 << 4)) else 'D2XX'\n        cfg['clock_polarity'] = 'high' if (cfg1 & self.CFG1.CLK_IDLE_STATE) \\\n                                else 'low'\n        cfg['lsb_data'] = bool(cfg1 & self.CFG1.DATA_LSB)\n        cfg['flow_control'] = 'on' if (cfg1 & self.CFG1.FLOW_CONTROL) \\\n                              else 'off'\n        cfg['powersave'] = bool(cfg1 & self.DRIVE.PWRSAVE_DIS)\n        max_drive = self.DRIVE.LOW.value | self.DRIVE.HIGH.value\n        for grp in range(2):\n            conf = self._eeprom[0x0c+grp]\n            cfg[f'group_{grp}_drive'] = 4 * (1+(conf & max_drive))\n            cfg[f'group_{grp}_schmitt'] = \\\n                bool(conf & self.DRIVE.SCHMITT.value)\n            cfg[f'group_{grp}_slow_slew'] = \\\n                bool(conf & self.DRIVE.SLOW_SLEW.value)\n        for bix in range(5):\n            value = self._eeprom[0x18 + bix]\n            low, high = value & 0x0F, value >> 4\n            try:\n                cfg[f'cbus_func_{(2*bix)+0}'] = self.CBUSH(low).name\n            except ValueError:\n                pass\n            try:\n                cfg[f'cbus_func_{(2*bix)+1}'] = self.CBUSH(high).name\n            except ValueError:\n                pass\n\n    def _decode_232r(self):\n        cfg = self._config\n        cfg0 = self._eeprom[0x00]\n        cfg['channel_a_driver'] = 'VCP' if (~cfg0 & (1 << 3)) else ''\n        cfg['high_current'] = bool(~cfg0 & (1 << 2))\n        cfg['external_oscillator'] = cfg0 & 0x02\n        for bit in self.UART_BITS:\n            value = self._eeprom[0x0B]\n            cfg[f'invert_{self.UART_BITS(bit).name}'] = bool(value & bit)\n        bix = 0\n        while True:\n            value = self._eeprom[0x14 + bix]\n            low, high = value & 0x0F, value >> 4\n            try:\n                cfg[f'cbus_func_{(2*bix)+0}'] = self.CBUS(low).name\n            except ValueError:\n                pass\n            if bix == 2:\n                break\n            try:\n                cfg[f'cbus_func_{(2*bix)+1}'] = self.CBUS(high).name\n            except ValueError:\n                pass\n            bix += 1\n\n    def _decode_2232h(self):\n        cfg = self._config\n        self._decode_x232h(cfg)\n        cfg0, cfg1 = self._eeprom[0x00], self._eeprom[0x01]\n        cfg['channel_a_type'] = self.CHANNEL(cfg0 & 0x7).name or 'UART'\n        cfg['channel_b_type'] = self.CHANNEL(cfg1 & 0x7).name or 'UART'\n        cfg['suspend_dbus7'] = bool(cfg1 & self.CFG1.SUSPEND_DBUS7.value)\n\n    def _decode_4232h(self):\n        cfg = self._config\n        self._decode_x232h(cfg)\n        cfg0, cfg1 = self._eeprom[0x00], self._eeprom[0x01]\n        cfg['channel_c_driver'] = 'VCP' if ((cfg0 >> 4) & (1 << 3)) else 'D2XX'\n        cfg['channel_d_driver'] = 'VCP' if ((cfg1 >> 4) & (1 << 3)) else 'D2XX'\n        conf = self._eeprom[0x0B]\n        rs485 = self.CHANNEL.RS485\n        for chix in range(4):\n            cfg[f'channel_{0xa+chix:x}_type'] = (\n                'RS485' if conf & (rs485 << chix) else 'UART')\n\n    def _decode_x232h(self, cfg):\n        # common code for 2232h, 4232h, 4232ha\n        cfg0, cfg1 = self._eeprom[0x00], self._eeprom[0x01]\n        cfg['channel_a_driver'] = 'VCP' if (cfg0 & (1 << 3)) else 'D2XX'\n        cfg['channel_b_driver'] = 'VCP' if (cfg1 & (1 << 3)) else 'D2XX'\n        max_drive = self.DRIVE.LOW.value | self.DRIVE.HIGH.value\n        for bix in range(4):\n            if not bix & 1:\n                val = self._eeprom[0x0c + bix//2]\n            else:\n                val >>= 4\n            cfg[f'group_{bix}_drive'] = 4 * (1+(val & max_drive))\n            cfg[f'group_{bix}_schmitt'] = \\\n                bool(val & self.DRIVE.SCHMITT.value)\n            cfg[f'group_{bix}_slow_slew'] = \\\n                bool(val & self.DRIVE.SLOW_SLEW.value)\n"
  },
  {
    "path": "pyftdi/ftdi.py",
    "content": "# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2016 Emmanuel Bouaziz <ebouaziz@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"FTDI core driver.\"\"\"\n\nfrom binascii import hexlify\nfrom collections import OrderedDict\nfrom enum import IntEnum, unique\nfrom errno import ENODEV\nfrom logging import getLogger, DEBUG\nfrom struct import unpack as sunpack\nfrom sys import platform\nfrom typing import Callable, Optional, List, Sequence, TextIO, Tuple, Union\nfrom usb.core import (Configuration as UsbConfiguration, Device as UsbDevice,\n                      USBError)\nfrom usb.util import (build_request_type, release_interface, CTRL_IN, CTRL_OUT,\n                      CTRL_TYPE_VENDOR, CTRL_RECIPIENT_DEVICE)\nfrom .misc import to_bool\nfrom .usbtools import UsbDeviceDescriptor, UsbTools\n\n# pylint: disable=invalid-name\n\n\nclass FtdiError(IOError):\n    \"\"\"Base class error for all FTDI device\"\"\"\n\n\nclass FtdiFeatureError(FtdiError):\n    \"\"\"Requested feature is not available on FTDI device\"\"\"\n\n\nclass FtdiMpsseError(FtdiFeatureError):\n    \"\"\"MPSSE mode not supported on FTDI device\"\"\"\n\n\nclass FtdiEepromError(FtdiError):\n    \"\"\"FTDI EEPROM access errors\"\"\"\n\n\nclass Ftdi:\n    \"\"\"FTDI device driver\"\"\"\n\n    SCHEME = 'ftdi'\n    \"\"\"URL scheme for :py:class:`UsbTools`.\"\"\"\n\n    FTDI_VENDOR = 0x403\n    \"\"\"USB VID for FTDI chips.\"\"\"\n\n    VENDOR_IDS = {'ftdi': FTDI_VENDOR}\n    \"\"\"Supported vendors, only FTDI.\n       To add third parties vendors see :py:meth:`add_custom_vendor`.\n    \"\"\"\n\n    PRODUCT_IDS = {\n        FTDI_VENDOR: OrderedDict((\n            # use an ordered dict so that the first occurence of a PID takes\n            # precedence when generating URLs - order does matter.\n            ('232', 0x6001),\n            ('232r', 0x6001),\n            ('232h', 0x6014),\n            ('2232', 0x6010),\n            ('2232c', 0x6010),\n            ('2232d', 0x6010),\n            ('2232h', 0x6010),\n            ('4232', 0x6011),\n            ('4232h', 0x6011),\n            ('ft-x', 0x6015),\n            ('230x', 0x6015),\n            ('231x', 0x6015),\n            ('234x', 0x6015),\n            ('4232ha', 0x6048),\n            ('ft232', 0x6001),\n            ('ft232r', 0x6001),\n            ('ft232h', 0x6014),\n            ('ft2232', 0x6010),\n            ('ft2232c', 0x6010),\n            ('ft2232d', 0x6010),\n            ('ft2232h', 0x6010),\n            ('ft4232', 0x6011),\n            ('ft4232h', 0x6011),\n            ('ft230x', 0x6015),\n            ('ft231x', 0x6015),\n            ('ft234x', 0x6015),\n            ('ft4232ha', 0x6048)))\n        }\n    \"\"\"Supported products, only FTDI officials ones.\n       To add third parties and customized products, see\n       :py:meth:`add_custom_product`.\n    \"\"\"\n\n    DEFAULT_VENDOR = FTDI_VENDOR\n    \"\"\"Default vendor: FTDI.\"\"\"\n\n    DEVICE_NAMES = {\n        0x0200: 'ft232am',\n        0x0400: 'ft232bm',\n        0x0500: 'ft2232c',\n        0x0600: 'ft232r',\n        0x0700: 'ft2232h',\n        0x0800: 'ft4232h',\n        0x0900: 'ft232h',\n        0x1000: 'ft-x',\n        0x3600: 'ft4232ha'}\n    \"\"\"Common names of FTDI supported devices.\"\"\"\n\n    # Note that the FTDI datasheets contradict themselves, so\n    # the following values may not be the right ones...\n    FIFO_SIZES = {\n        0x0200: (128, 128),    # FT232AM:   TX: 128, RX: 128\n        0x0400: (128, 384),    # FT232BM:   TX: 128, RX: 384\n        0x0500: (128, 384),    # FT2232C:   TX: 128, RX: 384\n        0x0600: (256, 128),    # FT232R:    TX: 256, RX: 128\n        0x0700: (4096, 4096),  # FT2232H:   TX: 4KiB, RX: 4KiB\n        0x0800: (2048, 2048),  # FT4232H:   TX: 2KiB, RX: 2KiB\n        0x0900: (1024, 1024),  # FT232H:    TX: 1KiB, RX: 1KiB\n        0x1000: (512, 512),    # FT-X:      TX: 512, RX: 512\n        0x3600: (2048, 2048),  # FT4232HA:  TX: 2KiB, RX: 2KiB\n    }\n    \"\"\"FTDI chip internal FIFO sizes\n\n       Note that 'TX' and 'RX' are inverted with the datasheet terminology:\n       Values here are seen from the host perspective, whereas datasheet\n       values are defined from the device perspective\n    \"\"\"\n\n    @unique\n    class BitMode(IntEnum):\n        \"\"\"Function selection.\"\"\"\n\n        RESET = 0x00    # switch off altnerative mode (default to UART)\n        BITBANG = 0x01  # classical asynchronous bitbang mode\n        MPSSE = 0x02    # MPSSE mode, available on 2232x chips\n        SYNCBB = 0x04   # synchronous bitbang mode\n        MCU = 0x08      # MCU Host Bus Emulation mode,\n        OPTO = 0x10     # Fast Opto-Isolated Serial Interface Mode\n        CBUS = 0x20     # Bitbang on CBUS pins of R-type chips\n        SYNCFF = 0x40   # Single Channel Synchronous FIFO mode\n\n    # MPSSE Commands\n    WRITE_BYTES_PVE_MSB = 0x10\n    WRITE_BYTES_NVE_MSB = 0x11\n    WRITE_BITS_PVE_MSB = 0x12\n    WRITE_BITS_NVE_MSB = 0x13\n    WRITE_BYTES_PVE_LSB = 0x18\n    WRITE_BYTES_NVE_LSB = 0x19\n    WRITE_BITS_PVE_LSB = 0x1a\n    WRITE_BITS_NVE_LSB = 0x1b\n    READ_BYTES_PVE_MSB = 0x20\n    READ_BYTES_NVE_MSB = 0x24\n    READ_BITS_PVE_MSB = 0x22\n    READ_BITS_NVE_MSB = 0x26\n    READ_BYTES_PVE_LSB = 0x28\n    READ_BYTES_NVE_LSB = 0x2c\n    READ_BITS_PVE_LSB = 0x2a\n    READ_BITS_NVE_LSB = 0x2e\n    RW_BYTES_PVE_NVE_MSB = 0x31\n    RW_BYTES_NVE_PVE_MSB = 0x34\n    RW_BITS_PVE_NVE_MSB = 0x33\n    RW_BITS_NVE_PVE_MSB = 0x36\n    RW_BYTES_PVE_NVE_LSB = 0x39\n    RW_BYTES_NVE_PVE_LSB = 0x3c\n    RW_BITS_PVE_NVE_LSB = 0x3b\n    RW_BITS_NVE_PVE_LSB = 0x3e\n    WRITE_BITS_TMS_PVE = 0x4a\n    WRITE_BITS_TMS_NVE = 0x4b\n    RW_BITS_TMS_PVE_PVE = 0x6a\n    RW_BITS_TMS_PVE_NVE = 0x6b\n    RW_BITS_TMS_NVE_PVE = 0x6e\n    RW_BITS_TMS_NVE_NVE = 0x6f\n    SEND_IMMEDIATE = 0x87\n    WAIT_ON_HIGH = 0x88\n    WAIT_ON_LOW = 0x89\n    READ_SHORT = 0x90\n    READ_EXTENDED = 0x91\n    WRITE_SHORT = 0x92\n    WRITE_EXTENDED = 0x93\n    # -H series only\n    DISABLE_CLK_DIV5 = 0x8a\n    ENABLE_CLK_DIV5 = 0x8b\n\n    # Modem status\n    MODEM_CTS = 1 << 4      # Clear to send\n    MODEM_DSR = 1 << 5      # Data set ready\n    MODEM_RI = 1 << 6       # Ring indicator\n    MODEM_RLSD = 1 << 7     # Carrier detect\n    MODEM_DR = 1 << 8       # Data ready\n    MODEM_OE = 1 << 9       # Overrun error\n    MODEM_PE = 1 << 10      # Parity error\n    MODEM_FE = 1 << 11      # Framing error\n    MODEM_BI = 1 << 12      # Break interrupt\n    MODEM_THRE = 1 << 13    # Transmitter holding register\n    MODEM_TEMT = 1 << 14    # Transmitter empty\n    MODEM_RCVE = 1 << 15    # Error in RCVR FIFO\n\n    # FTDI MPSSE commands\n    SET_BITS_LOW = 0x80     # Change LSB GPIO output\n    SET_BITS_HIGH = 0x82    # Change MSB GPIO output\n    GET_BITS_LOW = 0x81     # Get LSB GPIO output\n    GET_BITS_HIGH = 0x83    # Get MSB GPIO output\n    LOOPBACK_START = 0x84   # Enable loopback\n    LOOPBACK_END = 0x85     # Disable loopback\n    SET_TCK_DIVISOR = 0x86  # Set clock\n    # -H series only\n    ENABLE_CLK_3PHASE = 0x8c       # Enable 3-phase data clocking (I2C)\n    DISABLE_CLK_3PHASE = 0x8d      # Disable 3-phase data clocking\n    CLK_BITS_NO_DATA = 0x8e        # Allows JTAG clock to be output w/o data\n    CLK_BYTES_NO_DATA = 0x8f       # Allows JTAG clock to be output w/o data\n    CLK_WAIT_ON_HIGH = 0x94        # Clock until GPIOL1 is high\n    CLK_WAIT_ON_LOW = 0x95         # Clock until GPIOL1 is low\n    ENABLE_CLK_ADAPTIVE = 0x96     # Enable JTAG adaptive clock for ARM\n    DISABLE_CLK_ADAPTIVE = 0x97    # Disable JTAG adaptive clock\n    CLK_COUNT_WAIT_ON_HIGH = 0x9c  # Clock byte cycles until GPIOL1 is high\n    CLK_COUNT_WAIT_ON_LOW = 0x9d   # Clock byte cycles until GPIOL1 is low\n    # FT232H only\n    DRIVE_ZERO = 0x9e       # Drive-zero mode\n\n    # USB control requests\n    REQ_OUT = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR,\n                                 CTRL_RECIPIENT_DEVICE)\n    REQ_IN = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR,\n                                CTRL_RECIPIENT_DEVICE)\n\n    # Requests\n    SIO_REQ_RESET = 0x0              # Reset the port\n    SIO_REQ_SET_MODEM_CTRL = 0x1     # Set the modem control register\n    SIO_REQ_SET_FLOW_CTRL = 0x2      # Set flow control register\n    SIO_REQ_SET_BAUDRATE = 0x3       # Set baud rate\n    SIO_REQ_SET_DATA = 0x4           # Set the data characteristics of the port\n    SIO_REQ_POLL_MODEM_STATUS = 0x5  # Get line status\n    SIO_REQ_SET_EVENT_CHAR = 0x6     # Change event character\n    SIO_REQ_SET_ERROR_CHAR = 0x7     # Change error character\n    SIO_REQ_SET_LATENCY_TIMER = 0x9  # Change latency timer\n    SIO_REQ_GET_LATENCY_TIMER = 0xa  # Get latency timer\n    SIO_REQ_SET_BITMODE = 0xb        # Change bit mode\n    SIO_REQ_READ_PINS = 0xc          # Read GPIO pin value (or \"get bitmode\")\n\n    # Eeprom requests\n    SIO_REQ_EEPROM = 0x90\n    SIO_REQ_READ_EEPROM = SIO_REQ_EEPROM + 0   # Read EEPROM content\n    SIO_REQ_WRITE_EEPROM = SIO_REQ_EEPROM + 1  # Write EEPROM content\n    SIO_REQ_ERASE_EEPROM = SIO_REQ_EEPROM + 2  # Erase EEPROM content\n\n    # Reset arguments\n    SIO_RESET_SIO = 0        # Reset device\n    SIO_RESET_PURGE_RX = 1   # Drain USB RX buffer (host-to-ftdi)\n    SIO_RESET_PURGE_TX = 2   # Drain USB TX buffer (ftdi-to-host)\n\n    # Flow control arguments\n    SIO_DISABLE_FLOW_CTRL = 0x0\n    SIO_RTS_CTS_HS = 0x1 << 8\n    SIO_DTR_DSR_HS = 0x2 << 8\n    SIO_XON_XOFF_HS = 0x4 << 8\n    SIO_SET_DTR_MASK = 0x1\n    SIO_SET_DTR_HIGH = SIO_SET_DTR_MASK | (SIO_SET_DTR_MASK << 8)\n    SIO_SET_DTR_LOW = 0x0 | (SIO_SET_DTR_MASK << 8)\n    SIO_SET_RTS_MASK = 0x2\n    SIO_SET_RTS_HIGH = SIO_SET_RTS_MASK | (SIO_SET_RTS_MASK << 8)\n    SIO_SET_RTS_LOW = 0x0 | (SIO_SET_RTS_MASK << 8)\n\n    # Parity bits\n    PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE = range(5)\n    # Number of stop bits\n    STOP_BIT_1, STOP_BIT_15, STOP_BIT_2 = range(3)\n    # Number of bits\n    BITS_7, BITS_8 = [7+i for i in range(2)]\n    # Break type\n    BREAK_OFF, BREAK_ON = range(2)\n\n    # cts:  Clear to send\n    # dsr:  Data set ready\n    # ri:   Ring indicator\n    # dcd:  Data carrier detect\n    # dr:   Data ready\n    # oe:   Overrun error\n    # pe:   Parity error\n    # fe:   Framing error\n    # bi:   Break interrupt\n    # thre: Transmitter holding register empty\n    # temt: Transmitter empty\n    # err:  Error in RCVR FIFO\n    MODEM_STATUS = [('', '', '', '', 'cts', 'dsr', 'ri', 'dcd'),\n                    ('dr', 'overrun', 'parity', 'framing',\n                     'break', 'thre', 'txe', 'rcve')]\n\n    ERROR_BITS = (0x00, 0x8E)\n    TX_EMPTY_BITS = 0x60\n\n    # Clocks and baudrates\n    BUS_CLOCK_BASE = 6.0E6  # 6 MHz\n    BUS_CLOCK_HIGH = 30.0E6  # 30 MHz\n    BAUDRATE_REF_BASE = int(3.0E6)  # 3 MHz\n    BAUDRATE_REF_HIGH = int(12.0E6)  # 12 MHz\n    BITBANG_BAUDRATE_RATIO_BASE = 16\n    BITBANG_BAUDRATE_RATIO_HIGH = 5\n    BAUDRATE_TOLERANCE = 3.0  # acceptable clock drift for UART, in %\n\n    FRAC_DIV_CODE = (0, 3, 2, 4, 1, 5, 6, 7)\n\n    # Latency\n    LATENCY_MIN = 1\n    LATENCY_MAX = 255\n    LATENCY_EEPROM_FT232R = 77\n\n    # EEPROM Properties\n    EXT_EEPROM_SIZES = (128, 256)  # in bytes (93C66 seen as 93C56)\n\n    INT_EEPROMS = {\n        0x0600: 0x80,  # FT232R: 128 bytes, 1024 bits\n        0x1000: 0x400  # FT230*X: 1KiB\n    }\n\n    def __init__(self):\n        self.log = getLogger('pyftdi.ftdi')\n        self._debug_log = False\n        self._usb_dev = None\n        self._usb_read_timeout = 5000\n        self._usb_write_timeout = 5000\n        self._baudrate = -1\n        self._readbuffer = bytearray()\n        self._readoffset = 0\n        self._readbuffer_chunksize = 4 << 10  # 4KiB\n        self._writebuffer_chunksize = 4 << 10  # 4KiB\n        self._max_packet_size = 0\n        self._interface = None\n        self._index = None\n        self._in_ep = None\n        self._out_ep = None\n        self._bitmode = Ftdi.BitMode.RESET\n        self._latency = 0\n        self._latency_count = 0\n        self._latency_min = self.LATENCY_MIN\n        self._latency_max = self.LATENCY_MAX\n        self._latency_threshold = None  # disable dynamic latency\n        self._lineprop = 0\n        self._cbus_pins = (0, 0)\n        self._cbus_out = 0\n        self._tracer = None\n\n    # --- Public API -------------------------------------------------------\n\n    @classmethod\n    def create_from_url(cls, url: str) -> 'Ftdi':\n        \"\"\"Create an Ftdi instance from an URL\n\n           URL scheme: ftdi://[vendor[:product[:index|:serial]]]/interface\n\n           :param url: FTDI device selector\n           :return: a fresh, open Ftdi instance\n        \"\"\"\n        device = Ftdi()\n        device.open_from_url(url)\n        return device\n\n    @classmethod\n    def list_devices(cls, url: Optional[str] = None) -> \\\n            List[Tuple[UsbDeviceDescriptor, int]]:\n        \"\"\"List of URLs of connected FTDI devices.\n\n           :param url: a pattern URL to restrict the search\n           :return: list of (UsbDeviceDescriptor, interface)\n        \"\"\"\n        return UsbTools.list_devices(url or 'ftdi:///?',\n                                     cls.VENDOR_IDS, cls.PRODUCT_IDS,\n                                     cls.DEFAULT_VENDOR)\n\n    @classmethod\n    def show_devices(cls, url: Optional[str] = None,\n                     out: Optional[TextIO] = None) -> None:\n        \"\"\"Print the URLs and descriptors of connected FTDI devices.\n\n           :param url: a pattern URL to restrict the search\n           :param out: output stream, default to stdout\n        \"\"\"\n        devdescs = UsbTools.list_devices(url or 'ftdi:///?',\n                                         cls.VENDOR_IDS, cls.PRODUCT_IDS,\n                                         cls.DEFAULT_VENDOR)\n        UsbTools.show_devices('ftdi', cls.VENDOR_IDS, cls.PRODUCT_IDS,\n                              devdescs, out)\n\n    @classmethod\n    def get_identifiers(cls, url: str) -> Tuple[UsbDeviceDescriptor, int]:\n        \"\"\"Extract the identifiers of an FTDI device from URL, if any\n\n           :param url: input URL to parse\n        \"\"\"\n        return UsbTools.parse_url(url,\n                                  cls.SCHEME, cls.VENDOR_IDS, cls.PRODUCT_IDS,\n                                  cls.DEFAULT_VENDOR)\n\n    @classmethod\n    def get_device(cls, url: str) -> UsbDevice:\n        \"\"\"Get a USB device from its URL, without opening an instance.\n\n           :param url: input URL to parse\n           :return: the USB device that match the specified URL\n        \"\"\"\n        devdesc, _ = cls.get_identifiers(url)\n        return UsbTools.get_device(devdesc)\n\n    @classmethod\n    def add_custom_vendor(cls, vid: int, vidname: str = '') -> None:\n        \"\"\"Add a custom USB vendor identifier.\n\n           It can be useful to use a pretty URL for opening FTDI device\n\n           :param vid: Vendor ID (USB 16-bit identifier)\n           :param vidname: Vendor name (arbitrary string)\n           :raise ValueError: if the vendor id is already referenced\n        \"\"\"\n        if vid in cls.VENDOR_IDS.values():\n            raise ValueError(f'Vendor ID 0x{vid:04x} already registered')\n        if not vidname:\n            vidname = f'0x{vid:04x}'\n        cls.VENDOR_IDS[vidname] = vid\n\n    @classmethod\n    def add_custom_product(cls, vid: int, pid: int, pidname: str = '') -> None:\n        \"\"\"Add a custom USB product identifier.\n\n           It is required for opening FTDI device with non-standard VID/PID\n           USB identifiers.\n\n           :param vid: Vendor ID (USB 16-bit identifier)\n           :param pid: Product ID (USB 16-bit identifier)\n           :param pidname: Product name (arbitrary string)\n           :raise ValueError: if the product id is already referenced\n        \"\"\"\n        if vid not in cls.PRODUCT_IDS:\n            cls.PRODUCT_IDS[vid] = OrderedDict()\n        elif pid in cls.PRODUCT_IDS[vid].values():\n            raise ValueError(f'Product ID 0x{vid:04x}:0x{pid:04x} already '\n                             f'registered')\n        if not pidname:\n            pidname = f'0x{pid:04x}'\n        cls.PRODUCT_IDS[vid][pidname] = pid\n\n    @classmethod\n    def decode_modem_status(cls, value: bytes, error_only: bool = False) -> \\\n            Tuple[str, ...]:\n        \"\"\"Decode the FTDI modem status bitfield into short strings.\n\n           :param value: 2-byte mode status\n           :param error_only: only decode error flags\n           :return: a tuple of status identifiers\n        \"\"\"\n        status = []\n        for pos, (byte_, ebits) in enumerate(zip(value, cls.ERROR_BITS)):\n            for bit, _ in enumerate(cls.MODEM_STATUS[pos]):\n                if error_only:\n                    byte_ &= ebits\n                if byte_ & (1 << bit):\n                    status.append(cls.MODEM_STATUS[pos][bit])\n        return tuple(status)\n\n    @staticmethod\n    def find_all(vps: Sequence[Tuple[int, int]], nocache: bool = False) -> \\\n            List[Tuple[UsbDeviceDescriptor, int]]:\n        \"\"\"Find all devices that match the vendor/product pairs of the vps\n           list.\n\n           :param vps: a sequence of 2-tuple (vid, pid) pairs\n           :type vps: tuple(int, int)\n           :param bool nocache: bypass cache to re-enumerate USB devices on\n                                the host\n           :return: a list of 5-tuple (vid, pid, sernum, iface, description)\n                    device descriptors\n           :rtype: list(tuple(int,int,str,int,str))\n        \"\"\"\n        return UsbTools.find_all(vps, nocache)\n\n    @property\n    def is_connected(self) -> bool:\n        \"\"\"Tells whether this instance is connected to an actual USB slave.\n\n           :return: the slave connection status\n        \"\"\"\n        return bool(self._usb_dev)\n\n    def open_from_url(self, url: str) -> None:\n        \"\"\"Open a new interface to the specified FTDI device.\n\n           :param str url: a FTDI URL selector\n        \"\"\"\n        devdesc, interface = self.get_identifiers(url)\n        device = UsbTools.get_device(devdesc)\n        self.open_from_device(device, interface)\n\n    def open(self, vendor: int, product: int, bus: Optional[int] = None,\n             address: Optional[int] = None, index: int = 0,\n             serial: Optional[str] = None,\n             interface: int = 1) -> None:\n        \"\"\"Open a new interface to the specified FTDI device.\n\n           If several FTDI devices of the same kind (vid, pid) are connected\n           to the host, either index or serial argument should be used to\n           discriminate the FTDI device.\n\n           index argument is not a reliable solution as the host may enumerate\n           the USB device in random order. serial argument is more reliable\n           selector and should always be prefered.\n\n           Some FTDI devices support several interfaces/ports (such as FT2232H,\n           FT4232H and FT4232HA). The interface argument selects the FTDI port\n           to use, starting from 1 (not 0).\n\n           :param int vendor: USB vendor id\n           :param int product: USB product id\n           :param int bus: optional selector,  USB bus\n           :param int address: optional selector, USB address on bus\n           :param int index: optional selector, specified the n-th matching\n                             FTDI enumerated USB device on the host\n           :param str serial: optional selector, specified the FTDI device\n                              by its serial number\n           :param str interface: FTDI interface/port\n        \"\"\"\n        devdesc = UsbDeviceDescriptor(vendor, product, bus, address, serial,\n                                      index, None)\n        device = UsbTools.get_device(devdesc)\n        self.open_from_device(device, interface)\n\n    def open_from_device(self, device: UsbDevice,\n                         interface: int = 1) -> None:\n        \"\"\"Open a new interface from an existing USB device.\n\n           :param device: FTDI USB device (PyUSB instance)\n           :param interface: FTDI interface to use (integer starting from 1)\n        \"\"\"\n        if not isinstance(device, UsbDevice):\n            raise FtdiError(f\"Device '{device}' is not a PyUSB device\")\n        self._usb_dev = device\n        try:\n            self._usb_dev.set_configuration()\n        except USBError:\n            pass\n        # detect invalid interface as early as possible\n        config = self._usb_dev.get_active_configuration()\n        if interface > config.bNumInterfaces:\n            raise FtdiError(f'No such FTDI port: {interface}')\n        self._set_interface(config, interface)\n        self._max_packet_size = self._get_max_packet_size()\n        # Invalidate data in the readbuffer\n        self._readoffset = 0\n        self._readbuffer = bytearray()\n        # Drain input buffer\n        self.purge_buffers()\n        # Shallow reset\n        self._reset_device()\n        # Reset feature mode\n        self.set_bitmode(0, Ftdi.BitMode.RESET)\n        # Init latency\n        self._latency_threshold = None\n        self.set_latency_timer(self.LATENCY_MIN)\n        self._debug_log = self.log.getEffectiveLevel() == DEBUG\n\n    def close(self, freeze: bool = False) -> None:\n        \"\"\"Close the FTDI interface/port.\n\n           :param freeze: if set, FTDI port is not reset to its default\n                          state on close. This means the port is left with\n                          its current configuration and output signals.\n                          This feature should not be used except for very\n                          specific needs.\n        \"\"\"\n        if self._usb_dev:\n            dev = self._usb_dev\n            if self._is_pyusb_handle_active():\n                # Do not attempt to execute the following calls if the\n                # device has been closed: the ResourceManager may attempt\n                # to re-open the device that has been already closed, and\n                # this may lead to a (native) crash in libusb.\n                try:\n                    if not freeze:\n                        self.set_bitmode(0, Ftdi.BitMode.RESET)\n                        self.set_latency_timer(self.LATENCY_MAX)\n                    release_interface(dev, self._index - 1)\n                except FtdiError as exc:\n                    self.log.warning('FTDI device may be gone: %s', exc)\n                try:\n                    self._usb_dev.attach_kernel_driver(self._index - 1)\n                except (NotImplementedError, USBError):\n                    pass\n            self._usb_dev = None\n            UsbTools.release_device(dev)\n\n    def reset(self, usb_reset: bool = False) -> None:\n        \"\"\"Reset FTDI device.\n\n           :param usb_reset: wether to perform a full USB reset of the device.\n\n           Beware that selecting usb_reset performs a full USB device reset,\n           which means all other interfaces of the same device are also\n           affected.\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Not connected')\n        self._reset_device()\n        if usb_reset:\n            self._reset_usb_device()\n\n    def open_mpsse_from_url(self, url: str, direction: int = 0x0,\n                            initial: int = 0x0, frequency: float = 6.0E6,\n                            latency: int = 16, debug: bool = False) -> float:\n        \"\"\"Open a new interface to the specified FTDI device in MPSSE mode.\n\n           MPSSE enables I2C, SPI, JTAG or other synchronous serial interface\n           modes (vs. UART mode).\n\n           :param url: a FTDI URL selector\n           :param direction: a bitfield specifying the FTDI GPIO direction,\n                where high level defines an output, and low level defines an\n                input\n           :param initial: a bitfield specifying the initial output value\n           :param float frequency: serial interface clock in Hz\n           :param latency: low-level latency in milliseconds. The shorter\n                the delay, the higher the host CPU load. Do not use shorter\n                values than the default, as it triggers data loss in FTDI.\n           :param debug: use a tracer to decode MPSSE protocol\n           :return: actual bus frequency in Hz\n        \"\"\"\n        devdesc, interface = self.get_identifiers(url)\n        device = UsbTools.get_device(devdesc)\n        return self.open_mpsse_from_device(device, interface,\n                                           direction=direction,\n                                           initial=initial,\n                                           frequency=frequency,\n                                           latency=latency,\n                                           debug=debug)\n\n    def open_mpsse(self, vendor: int, product: int, bus: Optional[int] = None,\n                   address: Optional[int] = None, index: int = 0,\n                   serial: Optional[str] = None, interface: int = 1,\n                   direction: int = 0x0, initial: int = 0x0,\n                   frequency: float = 6.0E6, latency: int = 16,\n                   debug: bool = False) -> float:\n        \"\"\"Open a new interface to the specified FTDI device in MPSSE mode.\n\n           MPSSE enables I2C, SPI, JTAG or other synchronous serial interface\n           modes (vs. UART mode).\n\n           If several FTDI devices of the same kind (vid, pid) are connected\n           to the host, either index or serial argument should be used to\n           discriminate the FTDI device.\n\n           index argument is not a reliable solution as the host may enumerate\n           the USB device in random order. serial argument is more reliable\n           selector and should always be prefered.\n\n           Some FTDI devices support several interfaces/ports (such as FT2232H,\n           FT4232H and FT4232HA). The interface argument selects the FTDI port\n           to use, starting from 1 (not 0). Note that not all FTDI ports are\n           MPSSE capable.\n\n           :param vendor: USB vendor id\n           :param product: USB product id\n           :param bus: optional selector, USB bus\n           :param address: optional selector, USB address on bus\n           :param index: optional selector, specified the n-th matching\n                             FTDI enumerated USB device on the host\n           :param serial: optional selector, specified the FTDI device\n                              by its serial number\n           :param interface: FTDI interface/port\n           :param direction: a bitfield specifying the FTDI GPIO direction,\n                where high level defines an output, and low level defines an\n                input\n           :param initial: a bitfield specifying the initial output value\n           :param frequency: serial interface clock in Hz\n           :param latency: low-level latency in milliseconds. The shorter\n                the delay, the higher the host CPU load. Do not use shorter\n                values than the default, as it triggers data loss in FTDI.\n           :param bool debug: use a tracer to decode MPSSE protocol\n           :return: actual bus frequency in Hz\n        \"\"\"\n        devdesc = UsbDeviceDescriptor(vendor, product, bus, address, serial,\n                                      index, None)\n        device = UsbTools.get_device(devdesc)\n        return self.open_mpsse_from_device(device, interface,\n                                           direction=direction,\n                                           initial=initial,\n                                           frequency=frequency,\n                                           latency=latency,\n                                           debug=debug)\n\n    def open_mpsse_from_device(self, device: UsbDevice,\n                               interface: int = 1, direction: int = 0x0,\n                               initial: int = 0x0, frequency: float = 6.0E6,\n                               latency: int = 16, tracer: bool = False,\n                               debug: bool = False) -> float:\n        \"\"\"Open a new interface to the specified FTDI device in MPSSE mode.\n\n           MPSSE enables I2C, SPI, JTAG or other synchronous serial interface\n           modes (vs. UART mode).\n\n           If several FTDI devices of the same kind (vid, pid) are connected\n           to the host, either index or serial argument should be used to\n           discriminate the FTDI device.\n\n           index argument is not a reliable solution as the host may enumerate\n           the USB device in random order. serial argument is more reliable\n           selector and should always be prefered.\n\n           Some FTDI devices support several interfaces/ports (such as FT2232H,\n           FT4232H and FT4232HA). The interface argument selects the FTDI port\n           to use, starting from 1 (not 0). Note that not all FTDI ports are\n           MPSSE capable.\n\n           :param device: FTDI USB device\n           :param interface: FTDI interface/port\n           :param direction: a bitfield specifying the FTDI GPIO direction,\n                where high level defines an output, and low level defines an\n                input\n           :param initial: a bitfield specifying the initial output value\n           :param frequency: serial interface clock in Hz\n           :param latency: low-level latency in milliseconds. The shorter\n                the delay, the higher the host CPU load. Do not use shorter\n                values than the default, as it triggers data loss in FTDI.\n           :param bool tracer: use a tracer to decode MPSSE protocol\n           :param bool debug: add more debug traces\n           :return: actual bus frequency in Hz\n        \"\"\"\n        # pylint: disable=unused-argument\n        self.open_from_device(device, interface)\n        if not self.is_mpsse_interface(interface):\n            self.close()\n            raise FtdiMpsseError('This interface does not support MPSSE')\n        if to_bool(tracer):  # accept strings as boolean\n            # pylint: disable=import-outside-toplevel\n            from .tracer import FtdiMpsseTracer\n            self._tracer = FtdiMpsseTracer(self.device_version)\n            self.log.debug('Using MPSSE tracer')\n        # Set latency timer\n        self.set_latency_timer(latency)\n        # Set chunk size\n        self.write_data_set_chunksize()\n        self.read_data_set_chunksize()\n        # Reset feature mode\n        self.set_bitmode(0, Ftdi.BitMode.RESET)\n        # Drain buffers\n        self.purge_buffers()\n        # Disable event and error characters\n        self.set_event_char(0, False)\n        self.set_error_char(0, False)\n        # Enable MPSSE mode\n        self.set_bitmode(direction, Ftdi.BitMode.MPSSE)\n        # Configure clock\n        frequency = self._set_frequency(frequency)\n        # Configure I/O\n        cmd = bytearray((Ftdi.SET_BITS_LOW, initial & 0xFF, direction & 0xFF))\n        if self.has_wide_port:\n            initial >>= 8\n            direction >>= 8\n            cmd.extend((Ftdi.SET_BITS_HIGH, initial & 0xFF, direction & 0xFF))\n        self.write_data(cmd)\n        # Disable loopback\n        self.write_data(bytearray((Ftdi.LOOPBACK_END,)))\n        self.validate_mpsse()\n        # Return the actual frequency\n        return frequency\n\n    def open_bitbang_from_url(self, url: str, direction: int = 0x0,\n                              latency: int = 16, baudrate: int = 1000000,\n                              sync: bool = False) -> float:\n        \"\"\"Open a new interface to the specified FTDI device in bitbang mode.\n\n           Bitbang enables direct read or write to FTDI GPIOs.\n\n           :param url: a FTDI URL selector\n           :param direction: a bitfield specifying the FTDI GPIO direction,\n                where high level defines an output, and low level defines an\n                input\n           :param latency: low-level latency to select the USB FTDI poll\n                delay. The shorter the delay, the higher the host CPU load.\n           :param baudrate: pace to sequence GPIO exchanges\n           :param sync: whether to use synchronous or asynchronous bitbang\n           :return: actual bitbang baudrate in bps\n        \"\"\"\n        devdesc, interface = self.get_identifiers(url)\n        device = UsbTools.get_device(devdesc)\n        return self.open_bitbang_from_device(device, interface,\n                                             direction=direction,\n                                             latency=latency,\n                                             baudrate=baudrate,\n                                             sync=sync)\n\n    def open_bitbang(self, vendor: int, product: int,\n                     bus: Optional[int] = None, address: Optional[int] = None,\n                     index: int = 0, serial: Optional[str] = None,\n                     interface: int = 1, direction: int = 0x0,\n                     latency: int = 16, baudrate: int = 1000000,\n                     sync: bool = False) -> float:\n        \"\"\"Open a new interface to the specified FTDI device in bitbang mode.\n\n           Bitbang enables direct read or write to FTDI GPIOs.\n\n           :param vendor: USB vendor id\n           :param product: USB product id\n           :param index: optional selector, specified the n-th matching\n                             FTDI enumerated USB device on the host\n           :param serial: optional selector, specified the FTDI device\n                              by its serial number\n           :param interface: FTDI interface/port\n           :param direction: a bitfield specifying the FTDI GPIO direction,\n                where high level defines an output, and low level defines an\n                input\n           :param latency: low-level latency to select the USB FTDI poll\n                delay. The shorter the delay, the higher the host CPU load.\n           :param baudrate: pace to sequence GPIO exchanges\n           :param sync: whether to use synchronous or asynchronous bitbang\n           :return: actual bitbang baudrate in bps\n        \"\"\"\n        devdesc = UsbDeviceDescriptor(vendor, product, bus, address, serial,\n                                      index, None)\n        device = UsbTools.get_device(devdesc)\n        return self.open_bitbang_from_device(device, interface,\n                                             direction=direction,\n                                             latency=latency,\n                                             baudrate=baudrate,\n                                             sync=sync)\n\n    def open_bitbang_from_device(self, device: UsbDevice,\n                                 interface: int = 1, direction: int = 0x0,\n                                 latency: int = 16, baudrate: int = 1000000,\n                                 sync: bool = False) -> int:\n        \"\"\"Open a new interface to the specified FTDI device in bitbang mode.\n\n           Bitbang enables direct read or write to FTDI GPIOs.\n\n           :param device: FTDI USB device\n           :param interface: FTDI interface/port\n           :param direction: a bitfield specifying the FTDI GPIO direction,\n                where high level defines an output, and low level defines an\n                input\n           :param latency: low-level latency to select the USB FTDI poll\n                delay. The shorter the delay, the higher the host CPU load.\n           :param baudrate: pace to sequence GPIO exchanges\n           :param sync: whether to use synchronous or asynchronous bitbang\n           :return: actual bitbang baudrate in bps\n        \"\"\"\n        self.open_from_device(device, interface)\n        # Set latency timer\n        self.set_latency_timer(latency)\n        # Set chunk size\n        # Beware that RX buffer, over 512 bytes, contains 2-byte modem marker\n        # on every 512 byte chunk, so data and out-of-band marker get\n        # interleaved. This is not yet supported with read_data_bytes for now\n        self.write_data_set_chunksize()\n        self.read_data_set_chunksize()\n        # disable flow control\n        self.set_flowctrl('')\n        # Enable BITBANG mode\n        self.set_bitmode(direction, Ftdi.BitMode.BITBANG if not sync else\n                         Ftdi.BitMode.SYNCBB)\n        # Configure clock\n        if baudrate:\n            self._baudrate = self._set_baudrate(baudrate, False)\n        # Drain input buffer\n        self.purge_buffers()\n        return self._baudrate\n\n    @property\n    def usb_path(self) -> Tuple[int, int, int]:\n        \"\"\"Provide the physical location on the USB topology.\n\n           :return: a tuple of bus, address, interface; if connected\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Not connected')\n        return (self._usb_dev.bus, self._usb_dev.address,\n                self._interface.bInterfaceNumber)\n\n    @property\n    def device_version(self) -> int:\n        \"\"\"Report the device version, i.e. the kind of device.\n\n           :see: :py:meth:`ic_name` for a product version of this information.\n\n           :return: the device version (16-bit integer)\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self._usb_dev.bcdDevice\n\n    @property\n    def ic_name(self) -> str:\n        \"\"\"Return the current type of the FTDI device as a string\n\n           see also http://www.ftdichip.com/Support/\n           Documents/TechnicalNotes/TN_100_USB_VID-PID_Guidelines.pdf\n\n           :return: the identified FTDI device as a string\n        \"\"\"\n        if not self.is_connected:\n            return 'unknown'\n        return self.DEVICE_NAMES.get(self.device_version, 'undefined')\n\n    @property\n    def device_port_count(self) -> int:\n        \"\"\"Report the count of port/interface of the Ftdi device.\n\n           :return: the count of ports\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self._usb_dev.get_active_configuration().bNumInterfaces\n\n    @property\n    def port_index(self) -> int:\n        \"\"\"Report the port/interface index, starting from 1\n\n           :return: the port position/index\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self._index\n\n    @property\n    def port_width(self) -> int:\n        \"\"\"Report the width of a single port / interface\n\n           :return: the width of the port, in bits\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        if self.device_version in (0x0700, 0x0900):\n            return 16\n        if self.device_version in (0x0500, ):\n            return 12\n        return 8\n\n    @property\n    def has_mpsse(self) -> bool:\n        \"\"\"Tell whether the device supports MPSSE (I2C, SPI, JTAG, ...)\n\n           :return: True if the FTDI device supports MPSSE\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self.device_version in (0x0500, 0x0700, 0x0800, 0x0900, 0x3600)\n\n    @property\n    def has_wide_port(self) -> bool:\n        \"\"\"Tell whether the device supports 16-bit GPIO ports (vs. 8 bits)\n\n           :return: True if the FTDI device supports wide GPIO port\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        return self.port_width > 8\n\n    @property\n    def has_cbus(self) -> bool:\n        \"\"\"Tell whether the device supports CBUS bitbang.\n\n           CBUS bitbanging feature requires a special configuration in EEPROM.\n           This function only reports if the current device supports this mode,\n           not if this mode has been enabled in EEPROM.\n\n           EEPROM configuration must be queried to check which CBUS pins have\n           been configured for GPIO/bitbang mode.\n\n           :return: True if the FTDI device supports CBUS bitbang\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self.device_version in (0x0600, 0x0900, 0x1000)\n\n    @property\n    def has_drivezero(self) -> bool:\n        \"\"\"Tell whether the device supports drive-zero mode, i.e. if the\n           device supports the open-collector drive mode, useful for I2C\n           communication for example.\n\n           :return: True if the FTDI device features drive-zero mode\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self.device_version in (0x0900, )\n\n    @property\n    def is_legacy(self) -> bool:\n        \"\"\"Tell whether the device is a low-end FTDI\n\n           :return: True if the FTDI device can only be used as a slow USB-UART\n                    bridge\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self.device_version <= 0x0200\n\n    @property\n    def is_H_series(self) -> bool:\n        \"\"\"Tell whether the device is a high-end FTDI\n\n           :return: True if the FTDI device is a high-end USB-UART bridge\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        if not self.is_connected:\n            raise FtdiError('Device characteristics not yet known')\n        return self.device_version in (0x0700, 0x0800, 0x0900, 0x3600)\n\n    @property\n    def is_mpsse(self) -> bool:\n        \"\"\"Tell whether the device is configured in MPSSE mode\n\n           :return: True if the FTDI interface is configured in MPSSE mode\n        \"\"\"\n        return self._bitmode == Ftdi.BitMode.MPSSE\n\n    def is_mpsse_interface(self, interface: int) -> bool:\n        \"\"\"Tell whether the interface supports MPSSE (I2C, SPI, JTAG, ...)\n\n           :return: True if the FTDI interface supports MPSSE\n           :raise FtdiError: if no FTDI port is open\n        \"\"\"\n        if not self.has_mpsse:\n            return False\n        if self.device_version == 0x0800 and interface > 2:\n            return False\n        if self.device_version == 0x3600 and interface > 2:\n            return False\n        return True\n\n    @property\n    def is_bitbang_enabled(self) -> bool:\n        \"\"\"Tell whether some bitbang mode is activated\n\n           :return: True if the FTDI interface is configured to support\n                    bitbanging\n        \"\"\"\n        return self._bitmode not in (\n            Ftdi.BitMode.RESET,\n            Ftdi.BitMode.MPSSE,\n            Ftdi.BitMode.CBUS  # CBUS mode does not change base frequency\n        )\n\n    # legacy API\n    bitbang_enabled = is_bitbang_enabled\n\n    @property\n    def is_eeprom_internal(self) -> bool:\n        \"\"\"Tell whether the device has an internal EEPROM.\n\n           :return: True if the device has an internal EEPROM.\n        \"\"\"\n        return self.device_version in self.INT_EEPROMS\n\n    @property\n    def max_eeprom_size(self) -> int:\n        \"\"\"Report the maximum size of the EEPROM.\n           The actual size may be lower, of even 0 if no EEPROM is connected\n           or supported.\n\n           :return: the maximum size in bytes.\n        \"\"\"\n        if self.device_version in self.INT_EEPROMS:\n            return self.INT_EEPROMS[self.device_version]\n        if self.device_version == 0x0600:\n            return 0x80\n        return 0x100\n\n    @property\n    def frequency_max(self) -> float:\n        \"\"\"Tells the maximum frequency for MPSSE clock.\n\n           :return: the maximum supported frequency in Hz\n        \"\"\"\n        return Ftdi.BUS_CLOCK_HIGH if self.is_H_series else Ftdi.BUS_CLOCK_BASE\n\n    @property\n    def fifo_sizes(self) -> Tuple[int, int]:\n        \"\"\"Return the (TX, RX) tupple of hardware FIFO sizes\n\n           :return: 2-tuple of TX, RX FIFO size in bytes\n        \"\"\"\n        try:\n            return Ftdi.FIFO_SIZES[self.device_version]\n        except KeyError as exc:\n            raise FtdiFeatureError(f'Unsupported device: '\n                                   f'0x{self.device_version:04x}') from exc\n\n    @property\n    def mpsse_bit_delay(self) -> float:\n        \"\"\"Delay between execution of two MPSSE SET_BITS commands.\n\n           :return: minimum delay (actual value might be larger) in seconds\n        \"\"\"\n        # measured on FTDI2232H, not documented in datasheet, hence may vary\n        # from on FTDI model to another...\n        # left as a variable so it could be tweaked base on the FTDI bcd type,\n        # the frequency, or ... whatever else\n        return 0.5E-6  # seems to vary between 5 and 6.5 us\n\n    @property\n    def baudrate(self) -> int:\n        \"\"\"Return current baudrate.\n        \"\"\"\n        return self._baudrate\n\n    @property\n    def usb_dev(self) -> UsbDevice:\n        \"\"\"Return the underlying USB Device.\n        \"\"\"\n        return self._usb_dev\n\n    def set_baudrate(self, baudrate: int, constrain: bool = True) -> int:\n        \"\"\"Change the current UART or BitBang baudrate.\n\n           The FTDI device is not able to use an arbitrary baudrate. Its\n           internal dividors are only able to achieve some baudrates.\n\n           PyFtdi attemps to find the closest configurable baudrate and if\n           the deviation from the requested baudrate is too high, it rejects\n           the configuration if constrain is set.\n\n           :py:attr:`baudrate` attribute can be used to retrieve the exact\n           selected baudrate.\n\n           :py:const:`BAUDRATE_TOLERANCE` defines the maximum deviation between\n           the requested baudrate and the closest FTDI achieveable baudrate,\n           which matches standard UART clock drift (3%). If the achievable\n           baudrate is not within limits, baudrate setting is rejected.\n\n           :param baudrate: the new baudrate for the UART.\n           :param constrain: whether to validate baudrate is in RS232 tolerance\n                             limits or allow larger drift\n           :raise ValueError: if deviation from selected baudrate is too large\n           :raise FtdiError: on IO Error\n           :return: the effective baudrate\n        \"\"\"\n        self._baudrate = self._set_baudrate(baudrate, constrain)\n        return self._baudrate\n\n    def set_frequency(self, frequency: float) -> float:\n        \"\"\"Change the current MPSSE bus frequency\n\n           The FTDI device is not able to use an arbitrary frequency. Its\n           internal dividors are only able to achieve some frequencies.\n\n           PyFtdi finds and selects the closest configurable frequency.\n\n           :param frequency: the new frequency for the serial interface,\n                in Hz.\n           :return: the selected frequency, which may differ from the requested\n                one, in Hz\n        \"\"\"\n        return self._set_frequency(frequency)\n\n    def purge_rx_buffer(self) -> None:\n        \"\"\"Clear the USB receive buffer on the chip (host-to-ftdi) and the\n           internal read buffer.\"\"\"\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_RESET,\n                                   Ftdi.SIO_RESET_PURGE_RX):\n            raise FtdiError('Unable to flush RX buffer')\n        # Invalidate data in the readbuffer\n        self._readoffset = 0\n        self._readbuffer = bytearray()\n        self.log.debug('rx buf purged')\n\n    def purge_tx_buffer(self) -> None:\n        \"\"\"Clear the USB transmit buffer on the chip (ftdi-to-host).\"\"\"\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_RESET,\n                                   Ftdi.SIO_RESET_PURGE_TX):\n            raise FtdiError('Unable to flush TX buffer')\n\n    def purge_buffers(self) -> None:\n        \"\"\"Clear the buffers on the chip and the internal read buffer.\"\"\"\n        self.purge_rx_buffer()\n        self.purge_tx_buffer()\n\n    def write_data_set_chunksize(self, chunksize: int = 0) -> None:\n        \"\"\"Configure write buffer chunk size.\n\n           This is a low-level configuration option, which is not intended to\n           be use for a regular usage.\n\n           :param chunksize: the optional size of the write buffer in bytes,\n                             it is recommended to use 0 to force automatic\n                             evaluation of the best value.\n        \"\"\"\n        if chunksize == 0:\n            chunksize = self.fifo_sizes[0]\n        self._writebuffer_chunksize = chunksize\n        self.log.debug('TX chunksize: %d', self._writebuffer_chunksize)\n\n    def write_data_get_chunksize(self) -> int:\n        \"\"\"Get write buffer chunk size.\n\n           :return: the size of the write buffer in bytes\n        \"\"\"\n        return self._writebuffer_chunksize\n\n    def read_data_set_chunksize(self, chunksize: int = 0) -> None:\n        \"\"\"Configure read buffer chunk size.\n\n           This is a low-level configuration option, which is not intended to\n           be use for a regular usage.\n\n           :param chunksize: the optional size of the read buffer in bytes,\n                             it is recommended to use 0 to force automatic\n                             evaluation of the best value.\n        \"\"\"\n        # Invalidate all remaining data\n        self._readoffset = 0\n        self._readbuffer = bytearray()\n        if chunksize == 0:\n            # status byte prolog is emitted every maxpacketsize, but for \"some\"\n            # reasons, FT232R emits it every RX FIFO size bytes... Other\n            # devices use a maxpacketsize which is smaller or equal to their\n            # FIFO size, so this weird behavior is for now only experienced\n            # with FT232R. Any, the following compution should address all\n            # devices.\n            chunksize = min(self.fifo_sizes[0], self.fifo_sizes[1],\n                            self._max_packet_size)\n        if platform == 'linux':\n            chunksize = min(chunksize, 16384)\n        self._readbuffer_chunksize = chunksize\n        self.log.debug('RX chunksize: %d', self._readbuffer_chunksize)\n\n    def read_data_get_chunksize(self) -> int:\n        \"\"\"Get read buffer chunk size.\n\n           :return: the size of the write buffer in bytes\n        \"\"\"\n        return self._readbuffer_chunksize\n\n    def set_bitmode(self, bitmask: int, mode: 'Ftdi.BitMode') -> None:\n        \"\"\"Enable/disable bitbang modes.\n\n           Switch the FTDI interface to bitbang mode.\n        \"\"\"\n        self.log.debug('bitmode: %s', mode.name)\n        mask = sum(Ftdi.BitMode)\n        value = (bitmask & 0xff) | ((mode.value & mask) << 8)\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_BITMODE, value):\n            raise FtdiError('Unable to set bitmode')\n        self._bitmode = mode\n\n    def read_pins(self) -> int:\n        \"\"\"Directly read pin state, circumventing the read buffer.\n           Useful for bitbang mode.\n\n           :return: bitfield of FTDI interface input GPIO\n        \"\"\"\n        pins = self._ctrl_transfer_in(Ftdi.SIO_REQ_READ_PINS, 1)\n        if not pins:\n            raise FtdiError('Unable to read pins')\n        return pins[0]\n\n    def set_cbus_direction(self, mask: int, direction: int) -> None:\n        \"\"\"Configure the CBUS pins used as GPIOs\n\n           :param mask: which pins to configure as GPIOs\n           :param direction: which pins are output (vs. input)\n        \"\"\"\n        # sanity check: there cannot be more than 4 CBUS pins in bitbang mode\n        if not 0 <= mask <= 0x0F:\n            raise ValueError(f'Invalid CBUS gpio mask: 0x{mask:02x}')\n        if not 0 <= direction <= 0x0F:\n            raise ValueError(f'Invalid CBUS gpio direction: 0x{direction:02x}')\n        self._cbus_pins = (mask, direction)\n\n    def get_cbus_gpio(self) -> int:\n        \"\"\"Get the CBUS pins configured as GPIO inputs\n\n           :return: bitfield of CBUS read pins\n        \"\"\"\n        if self._bitmode not in (Ftdi.BitMode.RESET, Ftdi.BitMode.CBUS):\n            raise FtdiError('CBUS gpio not available from current mode')\n        if not self._cbus_pins[0] & ~self._cbus_pins[1]:\n            raise FtdiError('No CBUS IO configured as input')\n        outv = (self._cbus_pins[1] << 4) | self._cbus_out\n        oldmode = self._bitmode\n        try:\n            self.set_bitmode(outv, Ftdi.BitMode.CBUS)\n            inv = self.read_pins()\n        finally:\n            if oldmode != self._bitmode:\n                self.set_bitmode(0, oldmode)\n        return inv & ~self._cbus_pins[1] & self._cbus_pins[0]\n\n    def set_cbus_gpio(self, pins: int) -> None:\n        \"\"\"Set the CBUS pins configured as GPIO outputs\n\n           :param pins: bitfield to apply to CBUS output pins\n        \"\"\"\n        if self._bitmode not in (Ftdi.BitMode.RESET, Ftdi.BitMode.CBUS):\n            raise FtdiError('CBUS gpio not available from current mode')\n        # sanity check: there cannot be more than 4 CBUS pins in bitbang mode\n        if not 0 <= pins <= 0x0F:\n            raise ValueError(f'Invalid CBUS gpio pins: 0x{pins:02x}')\n        if not self._cbus_pins[0] & self._cbus_pins[1]:\n            raise FtdiError('No CBUS IO configured as output')\n        pins &= self._cbus_pins[0] & self._cbus_pins[1]\n        value = (self._cbus_pins[1] << 4) | pins\n        oldmode = self._bitmode\n        try:\n            self.set_bitmode(value, Ftdi.BitMode.CBUS)\n            self._cbus_out = pins\n        finally:\n            if oldmode != self._bitmode:\n                self.set_bitmode(0, oldmode)\n\n    def set_latency_timer(self, latency: int):\n        \"\"\"Set latency timer.\n\n           The FTDI chip keeps data in the internal buffer for a specific\n           amount of time if the buffer is not full yet to decrease\n           load on the usb bus.\n\n           The shorted the latency, the shorted the delay to obtain data and\n           the higher the host CPU load. Be careful with this option.\n\n           :param latency: latency (unspecified unit)\n        \"\"\"\n        if not Ftdi.LATENCY_MIN <= latency <= Ftdi.LATENCY_MAX:\n            raise ValueError(\"Latency out of range\")\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_LATENCY_TIMER, latency):\n            raise FtdiError('Unable to latency timer')\n\n    def get_latency_timer(self) -> int:\n        \"\"\"Get latency timer.\n\n           :return: the current latency (unspecified unit)\n        \"\"\"\n        latency = self._ctrl_transfer_in(Ftdi.SIO_REQ_GET_LATENCY_TIMER, 1)\n        if not latency:\n            raise FtdiError('Unable to get latency')\n        return latency[0]\n\n    def poll_modem_status(self) -> int:\n        \"\"\"Poll modem status information.\n\n           This function allows the retrieve the two status bytes of the\n           device, useful in UART mode.\n\n           FTDI device does not have a so-called USB \"interrupt\" end-point,\n           event polling on the UART interface is done through the regular\n           control endpoint.\n\n           see :py:func:`modem_status` to obtain decoded status strings\n\n           :return: modem status, as a proprietary bitfield\n        \"\"\"\n        value = self._ctrl_transfer_in(Ftdi.SIO_REQ_POLL_MODEM_STATUS, 2)\n        if not value or len(value) != 2:\n            raise FtdiError('Unable to get modem status')\n        status, = sunpack('<H', value)\n        return status\n\n    def modem_status(self) -> Tuple[str, ...]:\n        \"\"\"Provide the current modem status as a tuple of set signals\n\n           :return: decodede modem status as short strings\n        \"\"\"\n        value = self._ctrl_transfer_in(Ftdi.SIO_REQ_POLL_MODEM_STATUS, 2)\n        if not value or len(value) != 2:\n            raise FtdiError('Unable to get modem status')\n        return self.decode_modem_status(value)\n\n    def set_flowctrl(self, flowctrl: str) -> None:\n        \"\"\"Select flowcontrol in UART mode.\n\n           Either hardware flow control through RTS/CTS UART lines,\n           software or no flow control.\n\n           :param str flowctrl: either 'hw' for HW flow control or '' (empty\n                                string) for no flow control.\n           :raise ValueError: if the flow control argument is invalid\n\n           .. note:: How does RTS/CTS flow control work (from FTDI FAQ):\n\n                FTxxx RTS# pin is an output. It should be connected to the CTS#\n                input pin of the device at the other end of the UART link.\n\n                    * If RTS# is logic 0 it is indicating the FTxxx device can\n                      accept more data on the RXD pin.\n                    * If RTS# is logic 1 it is indicating the FTxxx device\n                      cannot accept more data.\n\n                RTS# changes state when the chip buffer reaches its last 32\n                bytes of space to allow time for the external device to stop\n                sending data to the FTxxx device.\n\n                FTxxx CTS# pin is an input. It should be connected to the RTS#\n                output pin of the device at the other end of the UART link.\n\n                  * If CTS# is logic 0 it is indicating the external device can\n                    accept more data, and the FTxxx will transmit on the TXD\n                    pin.\n                  * If CTS# is logic 1 it is indicating the external device\n                    cannot accept more data. the FTxxx will stop transmitting\n                    within 0~3 characters, depending on what is in the buffer.\n\n                    **This potential 3 character overrun does occasionally\n                    present problems.** Customers shoud be made aware the FTxxx\n                    is a USB device and not a \"normal\" RS232 device as seen on\n                    a PC. As such the device operates on a packet basis as\n                    opposed to a byte basis.\n\n                Word to the wise. Not only do RS232 level shifting devices\n                level shift, but they also invert the signal.\n        \"\"\"\n        ctrl = {'hw': Ftdi.SIO_RTS_CTS_HS,\n                '': Ftdi.SIO_DISABLE_FLOW_CTRL}\n        try:\n            value = ctrl[flowctrl] | self._index\n        except KeyError as exc:\n            raise ValueError(f'Unknown flow control: {flowctrl}') from exc\n        try:\n            if self._usb_dev.ctrl_transfer(\n                    Ftdi.REQ_OUT, Ftdi.SIO_REQ_SET_FLOW_CTRL, 0, value,\n                    bytearray(), self._usb_write_timeout):\n                raise FtdiError('Unable to set flow control')\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from exc\n\n    def set_dtr(self, state: bool) -> None:\n        \"\"\"Set dtr line\n\n           :param state: new DTR logical level\n        \"\"\"\n        value = Ftdi.SIO_SET_DTR_HIGH if state else Ftdi.SIO_SET_DTR_LOW\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_MODEM_CTRL, value):\n            raise FtdiError('Unable to set DTR line')\n\n    def set_rts(self, state: bool) -> None:\n        \"\"\"Set rts line\n\n           :param state: new RTS logical level\n        \"\"\"\n        value = Ftdi.SIO_SET_RTS_HIGH if state else Ftdi.SIO_SET_RTS_LOW\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_MODEM_CTRL, value):\n            raise FtdiError('Unable to set RTS line')\n\n    def set_dtr_rts(self, dtr: bool, rts: bool) -> None:\n        \"\"\"Set dtr and rts lines at once\n\n           :param dtr: new DTR logical level\n           :param rts: new RTS logical level\n        \"\"\"\n        value = 0\n        value |= Ftdi.SIO_SET_DTR_HIGH if dtr else Ftdi.SIO_SET_DTR_LOW\n        value |= Ftdi.SIO_SET_RTS_HIGH if rts else Ftdi.SIO_SET_RTS_LOW\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_MODEM_CTRL, value):\n            raise FtdiError('Unable to set DTR/RTS lines')\n\n    def set_break(self, break_: bool) -> None:\n        \"\"\"Start or stop a break exception event on the serial line\n\n           :param break_: either start or stop break event\n        \"\"\"\n        if break_:\n            value = self._lineprop | (0x01 << 14)\n            if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_DATA, value):\n                raise FtdiError('Unable to start break sequence')\n        else:\n            value = self._lineprop & ~(0x01 << 14)\n            if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_DATA, value):\n                raise FtdiError('Unable to stop break sequence')\n        self._lineprop = value\n\n    def set_event_char(self, eventch: int, enable: bool) -> None:\n        \"\"\"Set the special event character\"\"\"\n        value = eventch\n        if enable:\n            value |= 1 << 8\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_EVENT_CHAR, value):\n            raise FtdiError('Unable to set event char')\n\n    def set_error_char(self, errorch: int, enable: bool) -> None:\n        \"\"\"Set error character\"\"\"\n        value = errorch\n        if enable:\n            value |= 1 << 8\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_ERROR_CHAR, value):\n            raise FtdiError('Unable to set error char')\n\n    def set_line_property(self, bits: int, stopbit: Union[int, float],\n                          parity: str, break_: bool = False) -> None:\n        \"\"\"Configure the (RS232) UART characteristics.\n\n           Arguments match the valid subset for FTDI HW of pyserial\n           definitions.\n\n           Bits accepts one of the following values:\n\n           * ``7`` for 7-bit characters\n           * ``8`` for 8-bit characters\n\n           Stopbit accepts one of the following values:\n\n           * ``1`` for a single bit\n           * ``1.5`` for a bit and a half\n           * ``2`` for two bits\n\n           Parity accepts one of the following strings:\n\n           * ``N`` for no parity bit\n           * ``O`` for odd parity bit\n           * ``E`` for even parity bit\n           * ``M`` for parity bit always set\n           * ``S`` for parity bit always reset\n\n           :param bits: data bit count\n           :param stopbit: stop bit count\n           :param parity: parity mode as a single uppercase character\n           :param break_: force break event\n        \"\"\"\n        bytelength = {7: Ftdi.BITS_7,\n                      8: Ftdi.BITS_8}\n        parities = {'N': Ftdi.PARITY_NONE,\n                    'O': Ftdi.PARITY_ODD,\n                    'E': Ftdi.PARITY_EVEN,\n                    'M': Ftdi.PARITY_MARK,\n                    'S': Ftdi.PARITY_SPACE}\n        stopbits = {1: Ftdi.STOP_BIT_1,\n                    1.5: Ftdi.STOP_BIT_15,\n                    2: Ftdi.STOP_BIT_2}\n        if parity not in parities:\n            raise FtdiFeatureError(\"Unsupported parity\")\n        if bits not in bytelength:\n            raise FtdiFeatureError(\"Unsupported byte length\")\n        if stopbit not in stopbits:\n            raise FtdiFeatureError(\"Unsupported stop bits\")\n        value = bits & 0x0F\n        try:\n            value |= {Ftdi.PARITY_NONE: 0x00 << 8,\n                      Ftdi.PARITY_ODD: 0x01 << 8,\n                      Ftdi.PARITY_EVEN: 0x02 << 8,\n                      Ftdi.PARITY_MARK: 0x03 << 8,\n                      Ftdi.PARITY_SPACE: 0x04 << 8}[parities[parity]]\n            value |= {Ftdi.STOP_BIT_1: 0x00 << 11,\n                      Ftdi.STOP_BIT_15: 0x01 << 11,\n                      Ftdi.STOP_BIT_2: 0x02 << 11}[stopbits[stopbit]]\n            if break_ == Ftdi.BREAK_ON:\n                value |= 0x01 << 14\n        except KeyError as exc:\n            raise ValueError('Invalid line property') from exc\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_SET_DATA, value):\n            raise FtdiError('Unable to set line property')\n        self._lineprop = value\n\n    def enable_adaptive_clock(self, enable: bool = True) -> None:\n        \"\"\"Enable adaptative clock mode, useful in MPSEE mode.\n\n           Adaptive clock is a unique feature designed for a feedback clock\n           for JTAG with ARM core.\n\n           :param enable: whether to enable or disable this mode.\n           :raise FtdiMpsseError: if MPSSE mode is not enabled\n        \"\"\"\n        if not self.is_mpsse:\n            raise FtdiMpsseError('Setting adaptive clock mode is only '\n                                 'available from MPSSE mode')\n        self.write_data(bytearray([enable and Ftdi.ENABLE_CLK_ADAPTIVE or\n                                   Ftdi.DISABLE_CLK_ADAPTIVE]))\n\n    def enable_3phase_clock(self, enable: bool = True) -> None:\n        \"\"\"Enable 3-phase clocking mode, useful in MPSSE mode.\n\n           3-phase clock is mostly useful with I2C mode. It is also be used\n           as a workaround to support SPI mode 3.\n\n           :param enable: whether to enable or disable this mode.\n           :raise FtdiMpsseError: if MPSSE mode is not enabled or device is\n                not capable of 3-phase clocking\n        \"\"\"\n        if not self.is_mpsse:\n            raise FtdiMpsseError('Setting 3-phase clock mode is only '\n                                 'available from MPSSE mode')\n        if not self.is_H_series:\n            raise FtdiFeatureError('This device does not support 3-phase '\n                                   'clock')\n        self.write_data(bytearray([enable and Ftdi.ENABLE_CLK_3PHASE or\n                                   Ftdi.DISABLE_CLK_3PHASE]))\n\n    def enable_drivezero_mode(self, lines: int) -> None:\n        \"\"\"Enable drive-zero mode, useful in MPSSE mode.\n\n           drive-zero mode is mostly useful with I2C mode, to support the open\n           collector driving mode.\n\n           :param lines: bitfield of GPIO to drive in collector driven mode\n           :raise FtdiMpsseError: if MPSSE mode is not enabled or device is\n                not capable of drive-zero mode\n        \"\"\"\n        if not self.is_mpsse:\n            raise FtdiMpsseError('Setting drive-zero mode is only '\n                                 'available from MPSSE mode')\n        if not self.has_drivezero:\n            raise FtdiFeatureError('This device does not support drive-zero '\n                                   'mode')\n        self.write_data(bytearray([Ftdi.DRIVE_ZERO, lines & 0xff,\n                                   (lines >> 8) & 0xff]))\n\n    def enable_loopback_mode(self, loopback: bool = False) -> None:\n        \"\"\"Enable loopback, i.e. connect DO to DI in FTDI MPSSE port for test\n           purposes only. It does not support UART (TX to RX) mode.\n\n           :param loopback: whether to enable or disable this mode\n        \"\"\"\n        self.write_data(bytearray((Ftdi.LOOPBACK_START if loopback else\n                                   Ftdi.LOOPBACK_END,)))\n\n    def calc_eeprom_checksum(self, data: Union[bytes, bytearray]) -> int:\n        \"\"\"Calculate EEPROM checksum over the data\n\n           :param data: data to compute checksum over. Must be an even number\n                        of bytes\n           :return: checksum\n        \"\"\"\n        length = len(data)\n        if not length:\n            raise ValueError('No data to checksum')\n        if length & 0x1:\n            raise ValueError('Length not even')\n        # NOTE: checksum is computed using 16-bit values in little endian\n        # ordering\n        checksum = 0XAAAA\n        mtp = self.device_version == 0x1000  # FT230X\n        for idx in range(0, length, 2):\n            if mtp and 0x24 <= idx < 0x80:\n                # special MTP user section which is not considered for the CRC\n                continue\n            val = ((data[idx+1] << 8) + data[idx]) & 0xffff\n            checksum = val ^ checksum\n            checksum = ((checksum << 1) & 0xffff) | ((checksum >> 15) & 0xffff)\n        return checksum\n\n    def read_eeprom(self, addr: int = 0, length: Optional[int] = None,\n                    eeprom_size: Optional[int] = None) -> bytes:\n        \"\"\"Read the EEPROM starting at byte address, addr, and returning\n           length bytes. Here, addr and length are in bytes but we\n           access a 16-bit word at a time, so automatically update\n           addr and length to work with word accesses.\n\n           :param addr: byte address that desire to read.\n           :param length: byte length to read or None\n           :param eeprom_size: total size in bytes of the eeprom or None\n           :return: eeprom bytes, as an array of bytes\n        \"\"\"\n        eeprom_size = self._check_eeprom_size(eeprom_size)\n        if length is None:\n            length = eeprom_size\n        if addr < 0 or (addr+length) > eeprom_size:\n            raise ValueError('Invalid address/length')\n        word_addr = addr >> 1\n        word_count = length >> 1\n        if (addr & 0x1) | (length & 0x1):\n            word_count += 1\n        try:\n            data = bytearray()\n            while word_count:\n                buf = self._usb_dev.ctrl_transfer(\n                    Ftdi.REQ_IN, Ftdi.SIO_REQ_READ_EEPROM, 0,\n                    word_addr, 2, self._usb_read_timeout)\n                if not buf:\n                    err_addr = word_addr << 1\n                    raise FtdiEepromError(f'EEPROM read error @ {err_addr}')\n                data.extend(buf)\n                word_count -= 1\n                word_addr += 1\n            start = addr & 0x1\n            return bytes(data[start:start+length])\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from exc\n\n    def write_eeprom(self, addr: int, data: Union[bytes, bytearray],\n                     eeprom_size: Optional[int] = None,\n                     dry_run: bool = True) -> None:\n        \"\"\"Write multiple bytes to the EEPROM starting at byte address,\n           addr. This function also updates the checksum\n           automatically.\n\n           .. warning:: You can brick your device with invalid size or content.\n                        Use this function at your own risk, and RTFM.\n\n           :param addr: starting byte address to start writing\n           :param data: data to be written\n           :param eeprom_size: total size in bytes of the eeprom or None\n           :param dry_run: log what should be written, do not actually\n                           change the EEPROM content\n        \"\"\"\n        eeprom_size = self._check_eeprom_size(eeprom_size)\n        if not data:\n            return\n        length = len(data)\n        if addr < 0 or (addr+length) > eeprom_size:\n            # accept up to eeprom_size, even if the last two bytes are\n            # overwritten with a locally computed checksum\n            raise ValueError('Invalid address/length')\n        # First, read out the entire EEPROM, based on eeprom_size.\n        eeprom = bytearray(self.read_eeprom(0, eeprom_size))\n        # patch in the new data\n        eeprom[addr:addr+len(data)] = data\n        # compute new checksum\n        chksum = self.calc_eeprom_checksum(eeprom[:-2])\n        self.log.info('New EEPROM checksum: 0x%04x', chksum)\n        # insert updated checksum - it is last 16-bits in EEPROM\n        if self.device_version == 0x1000:\n            # FT230x EEPROM structure is different\n            eeprom[0x7e] = chksum & 0x0ff\n            eeprom[0x7f] = chksum >> 8\n        else:\n            eeprom[-2] = chksum & 0x0ff\n            eeprom[-1] = chksum >> 8\n        # Write back the new data and checksum back to\n        # EEPROM. Only write data that is changing instead of writing\n        # everything in EEPROM, even if the data does not change.\n        #\n        # Compute start and end sections of eeprom baring in mind that\n        # they must be even since it is a 16-bit EEPROM.\n        # If start addr is odd, back it up one.\n        start = addr\n        size = length\n        if start & 0x1:\n            start -= 1\n            size += 1\n        if size & 0x1:\n            size += 1\n        size = min(size, eeprom_size - 2)\n        # finally, write new section of data and ...\n        self._write_eeprom_raw(start, eeprom[start:start+size],\n                               dry_run=dry_run)\n        # ... updated checksum\n        self._write_eeprom_raw((eeprom_size-2), eeprom[-2:], dry_run=dry_run)\n\n    def overwrite_eeprom(self, data: Union[bytes, bytearray],\n                         dry_run: bool = True) -> None:\n        \"\"\"Write the whole EEPROM content, from first to last byte.\n\n           .. warning:: You can brick your device with invalid size or content.\n                        Use this function at your own risk, and RTFM.\n\n           :param data: data to be written (should include the checksum)\n           :param dry_run: log what should be written, do not actually\n                           change the EEPROM content\n        \"\"\"\n        if self.is_eeprom_internal:\n            eeprom_size = self.INT_EEPROMS[self.device_version]\n            if len(data) != eeprom_size:\n                raise ValueError('Invalid EEPROM size')\n        elif len(data) not in self.EXT_EEPROM_SIZES:\n            raise ValueError('Invalid EEPROM size')\n        self._write_eeprom_raw(0, data, dry_run=dry_run)\n\n    def write_data(self, data: Union[bytes, bytearray]) -> int:\n        \"\"\"Write data to the FTDI port.\n\n           In UART mode, data contains the serial stream to write to the UART\n           interface.\n\n           In MPSSE mode, data contains the sequence of MPSSE commands and\n           data.\n\n           Data buffer is split into chunk-sized blocks before being sent over\n           the USB bus.\n\n           :param data: the byte stream to send to the FTDI interface\n           :return: count of written bytes\n        \"\"\"\n        offset = 0\n        size = len(data)\n        try:\n            while offset < size:\n                write_size = self._writebuffer_chunksize\n                if offset + write_size > size:\n                    write_size = size - offset\n                length = self._write(data[offset:offset+write_size])\n                if length <= 0:\n                    raise FtdiError(\"Usb bulk write error\")\n                offset += length\n            return offset\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from exc\n\n    def read_data_bytes(self, size: int, attempt: int = 1,\n            request_gen: Optional[Callable[[int],\n                                  Union[bytes, bytearray]]] = None) \\\n        -> bytearray:\n        \"\"\"Read data from the FTDI interface\n\n           In UART mode, data contains the serial stream read from the UART\n           interface.\n\n           In MPSSE mode, data contains the sequence of data received and\n           processed with the MPSEE engine.\n\n           Data buffer is rebuilt from chunk-sized blocks received over the USB\n           bus.\n\n           FTDI device always sends internal status bytes, which are stripped\n           out as not part of the data payload.\n\n           Because of the multiple buses, buffers, FIFOs, and MPSSE command\n           processing, data might not be immediately available on the host\n           side. The attempt argument can be used to increase the attempt count\n           to retrieve the expected amount of data, before giving up and\n           returning all the received data, which may be shorted than the\n           requested amount.\n\n           :param size: the number of bytes to received from the device\n           :param attempt: attempt cycle count\n           :param request_gen: a callable that takes the number of bytes read\n                               and expect a bytes byffer to send back to the\n                               remote device. This is only useful to perform\n                               optimized/continuous transfer from a slave\n                               device.\n           :return: payload bytes, as bytes\n        \"\"\"\n        # Packet size sanity check\n        if not self._max_packet_size:\n            raise FtdiError(\"max_packet_size is bogus\")\n        packet_size = self._max_packet_size\n        length = 1  # initial condition to enter the usb_read loop\n        data = bytearray()\n        # everything we want is still in the cache?\n        if size <= len(self._readbuffer)-self._readoffset:\n            data = self._readbuffer[self._readoffset:self._readoffset+size]\n            self._readoffset += size\n            return data\n        # something still in the cache, but not enough to satisfy 'size'?\n        if len(self._readbuffer)-self._readoffset != 0:\n            data = self._readbuffer[self._readoffset:]\n            # end of readbuffer reached\n            self._readoffset = len(self._readbuffer)\n        # read from USB, filling in the local cache as it is empty\n        retry = attempt\n        req_size = size\n        try:\n            while (len(data) < size) and (length > 0):\n                while True:\n                    tempbuf = self._read()\n                    retry -= 1\n                    length = len(tempbuf)\n                    # the received buffer contains at least one useful databyte\n                    # (first 2 bytes in each packet represent the current modem\n                    # status)\n                    if length >= 2:\n                        if tempbuf[1] & self.TX_EMPTY_BITS:\n                            if request_gen:\n                                req_size -= length-2\n                                if req_size > 0:\n                                    cmd = request_gen(req_size)\n                                    if cmd:\n                                        self.write_data(cmd)\n                    if length > 2:\n                        retry = attempt\n                        if self._latency_threshold:\n                            self._adapt_latency(True)\n                        # skip the status bytes\n                        chunks = (length+packet_size-1) // packet_size\n                        count = packet_size - 2\n                        # if you want to show status, use the following code:\n                        status = tempbuf[:2]\n                        if status[1] & self.ERROR_BITS[1]:\n                            self.log.error(\n                                'FTDI error: %02x:%02x %s',\n                                status[0], status[1], (' '.join(\n                                    self.decode_modem_status(status,\n                                                             True)).title()))\n                        self._readbuffer = bytearray()\n                        self._readoffset = 0\n                        srcoff = 2\n                        for _ in range(chunks):\n                            self._readbuffer += tempbuf[srcoff:srcoff+count]\n                            srcoff += packet_size\n                        length = len(self._readbuffer)\n                        break\n                    # received buffer only contains the modem status bytes\n                    # no data received, may be late, try again\n                    if retry > 0:\n                        continue\n                    # no actual data\n                    self._readbuffer = bytearray()\n                    self._readoffset = 0\n                    if self._latency_threshold:\n                        self._adapt_latency(False)\n                    # no more data to read?\n                    return data\n                if length > 0:\n                    # data still fits in buf?\n                    if (len(data) + length) <= size:\n                        data += self._readbuffer[self._readoffset:\n                                                 self._readoffset+length]\n                        self._readoffset += length\n                        # did we read exactly the right amount of bytes?\n                        if len(data) == size:\n                            return data\n                    else:\n                        # partial copy, not enough bytes in the local cache to\n                        # fulfill the request\n                        part_size = min(size-len(data),\n                                        len(self._readbuffer)-self._readoffset)\n                        if part_size < 0:\n                            raise FtdiError(\"Internal Error\")\n                        data += self._readbuffer[self._readoffset:\n                                                 self._readoffset+part_size]\n                        self._readoffset += part_size\n                        return data\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from exc\n        # never reached\n        raise FtdiError(\"Internal error\")\n\n    def read_data(self, size: int) -> bytes:\n        \"\"\"Shortcut to received a bytes buffer instead of the array of bytes.\n\n           Note that output byte buffer may be shorted than the requested\n           size.\n\n           :param size: the number of bytes to received from the device\n           :return: payload bytes\n        \"\"\"\n        return bytes(self.read_data_bytes(size))\n\n    def get_cts(self) -> bool:\n        \"\"\"Read terminal status line: Clear To Send\n\n           :return: CTS line logical level\n        \"\"\"\n        status = self.poll_modem_status()\n        return bool(status & self.MODEM_CTS)\n\n    def get_dsr(self) -> bool:\n        \"\"\"Read terminal status line: Data Set Ready\n\n           :return: DSR line logical level\n        \"\"\"\n        status = self.poll_modem_status()\n        return bool(status & self.MODEM_DSR)\n\n    def get_ri(self) -> bool:\n        \"\"\"Read terminal status line: Ring Indicator\n\n           :return: RI line logical level\n        \"\"\"\n        status = self.poll_modem_status()\n        return bool(status & self.MODEM_RI)\n\n    def get_cd(self) -> bool:\n        \"\"\"Read terminal status line: Carrier Detect\n\n           :return: CD line logical level\n        \"\"\"\n        status = self.poll_modem_status()\n        return bool(status & self.MODEM_RLSD)\n\n    def set_dynamic_latency(self, lmin: int, lmax: int,\n                            threshold: int) -> None:\n        \"\"\"Set up or disable latency values.\n\n           Dynamic latency management is a load balancer to adapt the\n           responsiveness of FTDI read request vs. the host CPU load.\n\n           It is mostly useful in UART mode, so that read bandwidth can be\n           increased to the maximum achievable throughput, while maintaining\n           very low host CPU load when no data is received from the UART.\n\n           There should be no need to tweak the default values. Use with care.\n\n           Minimum latency is limited to 12 or above, at FTDI device starts\n           losing bytes when latency is too short...\n\n           Maximum latency value is 255 ms.\n\n           Polling latency is reset to `lmin` each time at least one payload\n           byte is received from the FTDI device.\n\n           It doubles, up to `lmax`, every `threshold` times no payload has\n           been received from the FTDI device.\n\n           :param lmin: minimum latency level (ms)\n           :param lmax: maximum latenty level (ms)\n           :param threshold: count to reset latency to maximum level\n        \"\"\"\n        if not threshold:\n            self._latency_count = 0\n            self._latency_threshold = None\n        else:\n            for lat in (lmin, lmax):\n                if not self.LATENCY_MIN <= lat <= self.LATENCY_MAX:\n                    raise ValueError(f'Latency out of range: {lat}')\n            self._latency_min = lmin\n            self._latency_max = lmax\n            self._latency_threshold = threshold\n            self._latency = lmin\n            self.set_latency_timer(self._latency)\n\n    def validate_mpsse(self) -> None:\n        \"\"\"Check that the previous MPSSE request has been accepted by the FTDI\n           device.\n\n           :raise FtdiError: if the FTDI device rejected the command.\n        \"\"\"\n        # only useful in MPSSE mode\n        bytes_ = self.read_data(2)\n        if (len(bytes_) >= 2) and (bytes_[0] == '\\xfa'):\n            raise FtdiError(f'Invalid command @ {bytes_[1]}')\n\n    @classmethod\n    def get_error_string(cls) -> str:\n        \"\"\"Wrapper for legacy compatibility.\n\n           :return: a constant, meaningless string\n        \"\"\"\n        return \"Unknown error\"\n\n    # --- Private implementation -------------------------------------------\n\n    def _set_interface(self, config: UsbConfiguration, ifnum: int):\n        \"\"\"Select the interface to use on the FTDI device\"\"\"\n        if ifnum == 0:\n            ifnum = 1\n        if ifnum-1 not in range(config.bNumInterfaces):\n            raise ValueError(\"No such interface for this device\")\n        self._interface = config[(ifnum-1, 0)]\n        self._index = self._interface.bInterfaceNumber+1\n        endpoints = sorted([ep.bEndpointAddress for ep in self._interface])\n        self._in_ep, self._out_ep = endpoints[:2]\n\n        # detach kernel driver from the interface\n        try:\n            if self._usb_dev.is_kernel_driver_active(self._index - 1):\n                self._usb_dev.detach_kernel_driver(self._index - 1)\n        except (NotImplementedError, USBError):\n            pass\n\n# pylint: disable=protected-access\n# need to access private member _ctx of PyUSB device (resource manager)\n# until PyUSB #302 is addressed\n\n    def _reset_usb_device(self) -> None:\n        \"\"\"Reset USB device (USB command, not FTDI specific).\"\"\"\n        self._usb_dev._ctx.backend.reset_device(self._usb_dev._ctx.handle)\n\n    def _is_pyusb_handle_active(self) -> bool:\n        # Unfortunately, we need to access pyusb ResourceManager\n        # and there is no public API for this.\n        return bool(self._usb_dev._ctx.handle)\n\n# pylint: enable-msg=protected-access\n\n    def _reset_device(self):\n        \"\"\"Reset the FTDI device (FTDI vendor command)\"\"\"\n        if self._ctrl_transfer_out(Ftdi.SIO_REQ_RESET,\n                                   Ftdi.SIO_RESET_SIO):\n            raise FtdiError('Unable to reset FTDI device')\n\n    def _ctrl_transfer_out(self, reqtype: int, value: int, data: bytes = b''):\n        \"\"\"Send a control message to the device\"\"\"\n        try:\n            return self._usb_dev.ctrl_transfer(\n                Ftdi.REQ_OUT, reqtype, value, self._index,\n                bytearray(data), self._usb_write_timeout)\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from None\n\n    def _ctrl_transfer_in(self, reqtype: int, length: int):\n        \"\"\"Request for a control message from the device\"\"\"\n        try:\n            return self._usb_dev.ctrl_transfer(\n                Ftdi.REQ_IN, reqtype, 0, self._index, length,\n                self._usb_read_timeout)\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from None\n\n    def _write(self, data: Union[bytes, bytearray]) -> int:\n        if self._debug_log:\n            try:\n                self.log.debug('> %s', hexlify(data).decode())\n            except TypeError as exc:\n                self.log.warning('> (invalid output byte sequence: %s)', exc)\n        if self._tracer:\n            self._tracer.send(self._index, data)\n        try:\n            return self._usb_dev.write(self._in_ep, data,\n                                       self._usb_write_timeout)\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from None\n\n    def _read(self) -> bytes:\n        try:\n            data = self._usb_dev.read(self._out_ep, self._readbuffer_chunksize,\n                                      self._usb_read_timeout)\n        except USBError as exc:\n            raise FtdiError(f'UsbError: {exc}') from None\n        if data:\n            if self._debug_log:\n                self.log.debug('< %s', hexlify(data).decode())\n            if self._tracer and len(data) > 2:\n                self._tracer.receive(self._index, data[2:])\n        return data\n\n    def _adapt_latency(self, payload_detected: bool) -> None:\n        \"\"\"Dynamic latency adaptation depending on the presence of a\n           payload in a RX buffer.\n\n           :param payload_detected: whether a payload has been received\n                                    within last RX buffer\n        \"\"\"\n        if payload_detected:\n            self._latency_count = 0\n            if self._latency != self._latency_min:\n                self.set_latency_timer(self._latency_min)\n                self._latency = self._latency_min\n            return\n        # no payload received\n        self._latency_count += 1\n        if self._latency != self._latency_max:\n            if self._latency_count > \\\n                    self._latency_threshold:\n                self._latency *= 2\n                if self._latency > self._latency_max:\n                    self._latency = self._latency_max\n                else:\n                    self._latency_count = 0\n                self.set_latency_timer(self._latency)\n\n    def _check_eeprom_size(self, eeprom_size: Optional[int]) -> int:\n        if self.device_version in self.INT_EEPROMS:\n            if (eeprom_size and\n                    eeprom_size != self.INT_EEPROMS[self.device_version]):\n                raise ValueError(f'Invalid EEPROM size: {eeprom_size}')\n            eeprom_size = self.INT_EEPROMS[self.device_version]\n        else:\n            if eeprom_size is None:\n                eeprom_size = self.max_eeprom_size\n            if eeprom_size not in self.EXT_EEPROM_SIZES:\n                raise ValueError(f'Invalid EEPROM size: {eeprom_size}')\n        return eeprom_size\n\n    def _write_eeprom_raw(self, addr: int, data: Union[bytes, bytearray],\n                          dry_run: bool = True) -> None:\n        \"\"\"Write multiple bytes to the EEPROM starting at byte address,\n           addr. Length of data must be a multiple of 2 since the\n           EEPROM is 16-bits. So automatically extend data by 1 byte\n           if this is not the case.\n\n           :param int addr: starting byte address to start writing\n           :param bytes data: data to be written\n           :param dry_run: log what should be written, do not actually\n                           change the EEPROM content\n        \"\"\"\n        if self.device_version == 0x0600:\n            # FT232R internal EEPROM is unstable and latency timer seems\n            # to have a direct impact on EEPROM programming...\n            latency = self.get_latency_timer()\n        else:\n            latency = 0\n        try:\n            if latency:\n                self.set_latency_timer(self.LATENCY_EEPROM_FT232R)\n            length = len(data)\n            if addr & 0x1 or length & 0x1:\n                raise ValueError('Address/length not even')\n            for word in sunpack(f'<{length//2}H', data):\n                if not dry_run:\n                    out = self._usb_dev.ctrl_transfer(\n                        Ftdi.REQ_OUT, Ftdi.SIO_REQ_WRITE_EEPROM,\n                        word, addr >> 1, b'', self._usb_write_timeout)\n                    if out:\n                        raise FtdiEepromError(f'EEPROM Write Error @ {addr}')\n                    self.log.debug('Write EEPROM [0x%02x]: 0x%04x', addr, word)\n                else:\n                    self.log.info('Fake write EEPROM [0x%02x]: 0x%04x',\n                                  addr, word)\n                addr += 2\n        finally:\n            if latency:\n                self.set_latency_timer(latency)\n\n    def _get_max_packet_size(self) -> int:\n        \"\"\"Retrieve the maximum length of a data packet\"\"\"\n        if not self.is_connected:\n            raise IOError(\"Device is not yet known\", ENODEV)\n        if not self._interface:\n            raise IOError(\"Interface is not yet known\", ENODEV)\n        endpoint = self._interface[0]\n        packet_size = endpoint.wMaxPacketSize\n        return packet_size\n\n    def _convert_baudrate_legacy(self, baudrate: int) -> Tuple[int, int, int]:\n        if baudrate > self.BAUDRATE_REF_BASE:\n            raise ValueError('Invalid baudrate (too high)')\n        div8 = int(round((8 * self.BAUDRATE_REF_BASE) / baudrate))\n        if (div8 & 0x7) == 7:\n            div8 += 1\n        div = div8 >> 3\n        div8 &= 0x7\n        if div8 == 1:\n            div |= 0xc000\n        elif div8 >= 4:\n            div |= 0x4000\n        elif div8 != 0:\n            div |= 0x8000\n        elif div == 1:\n            div = 0\n        value = div & 0xFFFF\n        index = (div >> 16) & 0xFFFF\n        estimate = int(((8 * self.BAUDRATE_REF_BASE) + (div8//2))//div8)\n        return estimate, value, index\n\n    def _convert_baudrate(self, baudrate: int) -> Tuple[int, int, int]:\n        \"\"\"Convert a requested baudrate into the closest possible baudrate\n           that can be assigned to the FTDI device\n\n           :param baudrate: the baudrate in bps\n           :return: a 3-uple of the apprimated baudrate, the value and index\n                    to use as the USB configuration parameter\n        \"\"\"\n        if self.device_version == 0x200:\n            return self._convert_baudrate_legacy(baudrate)\n        if self.is_H_series and baudrate >= 1200:\n            hispeed = True\n            clock = self.BAUDRATE_REF_HIGH\n            bb_ratio = self.BITBANG_BAUDRATE_RATIO_HIGH\n        else:\n            hispeed = False\n            clock = self.BAUDRATE_REF_BASE\n            bb_ratio = self.BITBANG_BAUDRATE_RATIO_BASE\n        if baudrate > clock:\n            raise ValueError('Invalid baudrate (too high)')\n        if baudrate < ((clock >> 14) + 1):\n            raise ValueError('Invalid baudrate (too low)')\n        if self.is_bitbang_enabled:\n            baudrate //= bb_ratio\n        div8 = int(round((8 * clock) / baudrate))\n        div = div8 >> 3\n        div |= self.FRAC_DIV_CODE[div8 & 0x7] << 14\n        if div == 1:\n            div = 0\n        elif div == 0x4001:\n            div = 1\n        if hispeed:\n            div |= 0x00020000\n        value = div & 0xFFFF\n        index = (div >> 16) & 0xFFFF\n        if self.device_version >= 0x0700 or self.device_version == 0x0500:\n            index <<= 8\n            index |= self._index\n        estimate = int(((8 * clock) + (div8//2))//div8)\n        if self.is_bitbang_enabled:\n            estimate *= bb_ratio\n        return estimate, value, index\n\n    def _set_baudrate(self, baudrate: int, constrain: bool) -> int:\n        if self.is_mpsse:\n            raise FtdiFeatureError('Cannot change frequency w/ current mode')\n        actual, value, index = self._convert_baudrate(baudrate)\n        delta = 100*abs(float(actual-baudrate))/baudrate\n        self.log.debug('Actual baudrate: %d %.1f%% div [%04x:%04x]',\n                       actual, delta, index, value)\n        # return actual\n        if constrain and delta > Ftdi.BAUDRATE_TOLERANCE:\n            raise ValueError(f'Baudrate tolerance exceeded: {delta:.02f}% '\n                             f'(wanted {baudrate}, achievable {actual})')\n        try:\n            if self._usb_dev.ctrl_transfer(\n                    Ftdi.REQ_OUT, Ftdi.SIO_REQ_SET_BAUDRATE, value, index,\n                    bytearray(), self._usb_write_timeout):\n                raise FtdiError('Unable to set baudrate')\n            return actual\n        except USBError as exc:\n            raise FtdiError('UsbError: {exc}') from exc\n\n    def _set_frequency(self, frequency: float) -> float:\n        \"\"\"Convert a frequency value into a TCK divisor setting\"\"\"\n        if not self.is_mpsse:\n            raise FtdiFeatureError('Cannot change frequency w/ current mode')\n        if frequency > self.frequency_max:\n            raise FtdiFeatureError(f'Unsupported frequency: {frequency:.0f}')\n        # Calculate base speed clock divider\n        divcode = Ftdi.ENABLE_CLK_DIV5\n        divisor = int((Ftdi.BUS_CLOCK_BASE+frequency/2)/frequency)-1\n        divisor = max(0, min(0xFFFF, divisor))\n        actual_freq = Ftdi.BUS_CLOCK_BASE/(divisor+1)\n        error = (actual_freq/frequency)-1\n        # Should we use high speed clock available in H series?\n        if self.is_H_series:\n            # Calculate high speed clock divider\n            divisor_hs = int((Ftdi.BUS_CLOCK_HIGH+frequency/2)/frequency)-1\n            divisor_hs = max(0, min(0xFFFF, divisor_hs))\n            actual_freq_hs = Ftdi.BUS_CLOCK_HIGH/(divisor_hs+1)\n            error_hs = (actual_freq_hs/frequency)-1\n            # Enable if closer to desired frequency (percentually)\n            if abs(error_hs) < abs(error):\n                divcode = Ftdi.DISABLE_CLK_DIV5\n                divisor = divisor_hs\n                actual_freq = actual_freq_hs\n                error = error_hs\n        # FTDI expects little endian\n        if self.is_H_series:\n            cmd = bytearray((divcode,))\n        else:\n            cmd = bytearray()\n        cmd.extend((Ftdi.SET_TCK_DIVISOR, divisor & 0xff,\n                    (divisor >> 8) & 0xff))\n        self.write_data(cmd)\n        self.validate_mpsse()\n        # Drain input buffer\n        self.purge_rx_buffer()\n        # Note that bus frequency may differ from clock frequency, when\n        # 3-phase clock is enable, in which case bus frequency = 2/3 clock\n        # frequency\n        if actual_freq > 1E6:\n            self.log.debug('Clock frequency: %.6f MHz (error: %+.1f %%)',\n                           (actual_freq/1E6), error*100)\n        else:\n            self.log.debug('Clock frequency: %.3f KHz (error: %+.1f %%)',\n                           (actual_freq/1E3), error*100)\n        return actual_freq\n\n    def __get_timeouts(self) -> Tuple[int, int]:\n        return self._usb_read_timeout, self._usb_write_timeout\n\n    def __set_timeouts(self, timeouts: Tuple[int, int]):\n        (read_timeout, write_timeout) = timeouts\n        self._usb_read_timeout = read_timeout\n        self._usb_write_timeout = write_timeout\n\n    timeouts = property(__get_timeouts, __set_timeouts)\n"
  },
  {
    "path": "pyftdi/gpio.py",
    "content": "# Copyright (c) 2014-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2016, Emmanuel Bouaziz <ebouaziz@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"GPIO/BitBang support for PyFdti\"\"\"\n\n\nfrom struct import calcsize as scalc, unpack as sunpack\nfrom typing import Iterable, Optional, Tuple, Union\nfrom .ftdi import Ftdi, FtdiError\nfrom .misc import is_iterable\n\n\nclass GpioException(FtdiError):\n    \"\"\"Base class for GPIO errors.\n    \"\"\"\n\n\nclass GpioPort:\n    \"\"\"Duck-type GPIO port for GPIO all controllers.\n    \"\"\"\n\n\nclass GpioBaseController(GpioPort):\n    \"\"\"GPIO controller for an FTDI port, in bit-bang legacy mode.\n\n       GPIO bit-bang mode is limited to the 8 lower pins of each GPIO port.\n    \"\"\"\n\n    def __init__(self):\n        self._ftdi = Ftdi()\n        self._direction = 0\n        self._width = 0\n        self._mask = 0\n        self._frequency = 0\n\n    @property\n    def ftdi(self) -> Ftdi:\n        \"\"\"Return the Ftdi instance.\n\n           :return: the Ftdi instance\n        \"\"\"\n        return self._ftdi\n\n    @property\n    def is_connected(self) -> bool:\n        \"\"\"Reports whether a connection exists with the FTDI interface.\n\n           :return: the FTDI slave connection status\n        \"\"\"\n        return self._ftdi.is_connected\n\n    def configure(self, url: str, direction: int = 0,\n                  **kwargs) -> int:\n        \"\"\"Open a new interface to the specified FTDI device in bitbang mode.\n\n           :param str url: a FTDI URL selector\n           :param int direction: a bitfield specifying the FTDI GPIO direction,\n                where high level defines an output, and low level defines an\n                input\n           :param initial: optional initial GPIO output value\n           :param pace: optional pace in GPIO sample per second\n           :return: actual bitbang pace in sample per second\n        \"\"\"\n        if self.is_connected:\n            raise FtdiError('Already connected')\n        kwargs = dict(kwargs)\n        frequency = kwargs.get('frequency', None)\n        if frequency is None:\n            frequency = kwargs.get('baudrate', None)\n        for k in ('direction', 'sync', 'frequency', 'baudrate'):\n            if k in kwargs:\n                del kwargs[k]\n        self._frequency = self._configure(url, direction, frequency, **kwargs)\n\n    def close(self, freeze: bool = False) -> None:\n        \"\"\"Close the GPIO port.\n\n           :param freeze: if set, FTDI port is not reset to its default\n                          state on close. This means the port is left with\n                          its current configuration and output signals.\n                          This feature should not be used except for very\n                          specific needs.\n        \"\"\"\n        if self._ftdi.is_connected:\n            self._ftdi.close(freeze)\n\n    def get_gpio(self) -> GpioPort:\n        \"\"\"Retrieve the GPIO port.\n\n           This method is mostly useless, it is a wrapper to duck type other\n           GPIO APIs (I2C, SPI, ...)\n\n           :return: GPIO port\n        \"\"\"\n        return self\n\n    @property\n    def direction(self) -> int:\n        \"\"\"Reports the GPIO direction.\n\n          :return: a bitfield specifying the FTDI GPIO direction, where high\n                level reports an output pin, and low level reports an input pin\n        \"\"\"\n        return self._direction\n\n    @property\n    def pins(self) -> int:\n        \"\"\"Report the configured GPIOs as a bitfield.\n\n           A true bit represents a GPIO, a false bit a reserved or not\n           configured pin.\n\n           :return: always 0xFF for GpioController instance.\n        \"\"\"\n        return self._mask\n\n    @property\n    def all_pins(self) -> int:\n        \"\"\"Report the addressable GPIOs as a bitfield.\n\n           A true bit represents a pin which may be used as a GPIO, a false bit\n           a reserved pin\n\n           :return: always 0xFF for GpioController instance.\n        \"\"\"\n        return self._mask\n\n    @property\n    def width(self) -> int:\n        \"\"\"Report the FTDI count of addressable pins.\n\n           :return: the width of the GPIO port.\n        \"\"\"\n        return self._width\n\n    @property\n    def frequency(self) -> float:\n        \"\"\"Return the pace at which sequence of GPIO samples are read\n           and written.\n        \"\"\"\n        return self._frequency\n\n    def set_frequency(self, frequency: Union[int, float]) -> None:\n        \"\"\"Set the frequency at which sequence of GPIO samples are read\n           and written.\n\n           :param frequency: the new frequency, in GPIO samples per second\n        \"\"\"\n        raise NotImplementedError('GpioBaseController cannot be instanciated')\n\n    def set_direction(self, pins: int, direction: int) -> None:\n        \"\"\"Update the GPIO pin direction.\n\n           :param pins: which GPIO pins should be reconfigured\n           :param direction: a bitfield of GPIO pins. Each bit represent a\n                GPIO pin, where a high level sets the pin as output and a low\n                level sets the pin as input/high-Z.\n        \"\"\"\n        if direction > self._mask:\n            raise GpioException(\"Invalid direction mask\")\n        self._direction &= ~pins\n        self._direction |= (pins & direction)\n        self._update_direction()\n\n    def _configure(self, url: str, direction: int,\n                   frequency: Union[int, float, None] = None, **kwargs) -> int:\n        raise NotImplementedError('GpioBaseController cannot be instanciated')\n\n    def _update_direction(self) -> None:\n        raise NotImplementedError('Missing implementation')\n\n\nclass GpioAsyncController(GpioBaseController):\n    \"\"\"GPIO controller for an FTDI port, in bit-bang asynchronous mode.\n\n       GPIO accessible pins are limited to the 8 lower pins of each GPIO port.\n\n       Asynchronous bitbang output are updated on write request using the\n       :py:meth:`write` method, clocked at the selected frequency.\n\n       Asynchronous bitbang input are sampled at the same rate, as soon as the\n       controller is initialized. The GPIO input samples fill in the FTDI HW\n       buffer until it is filled up, in which case sampling stops until the\n       GPIO samples are read out with the :py:meth:`read` method. It may be\n       therefore hard to use, except if peek mode is selected,\n       see :py:meth:`read` for details.\n\n       Note that FTDI internal clock divider cannot generate any arbitrary\n       frequency, so the closest frequency to the request one that can be\n       generated is selected. The actual :py:attr:`frequency` may be tested to\n       check if it matches the board requirements.\n    \"\"\"\n\n    def read(self, readlen: int = 1, peek: Optional[bool] = None,\n             noflush: bool = False) -> Union[int, bytes]:\n        \"\"\"Read the GPIO input pin electrical level.\n\n           :param readlen: how many GPIO samples to retrieve. Each sample is\n                           8-bit wide.\n           :param peek: whether to peek/sample the instantaneous GPIO pin\n                        values from port, or to use the HW FIFO. The HW FIFO is\n                        continously filled up with GPIO sample at the current\n                        frequency, until it is full - samples are no longer\n                        collected until the FIFO is read. This means than\n                        non-peek mode read \"old\" values, with no way to know at\n                        which time they have been sampled. PyFtdi ensures that\n                        old sampled values before the completion of a previous\n                        GPIO write are discarded. When peek mode is selected,\n                        readlen should be 1.\n           :param noflush: whether to disable the RX buffer flush before\n                           reading out data\n           :return: a 8-bit wide integer if peek mode is used, or\n                    a bytes buffer otherwise.\n        \"\"\"\n        if not self.is_connected:\n            raise GpioException('Not connected')\n        if peek is None and readlen == 1:\n            # compatibility with legacy API\n            peek = True\n        if peek:\n            if readlen != 1:\n                raise ValueError('Invalid read length with peek mode')\n            return self._ftdi.read_pins()\n        # in asynchronous bitbang mode, the FTDI-to-host FIFO is filled in\n        # continuously once this mode is activated. This means there is no\n        # way to trigger the exact moment where the buffer is filled in, nor\n        # to define the write pointer in the buffer. Reading out this buffer\n        # at any time is likely to contain a mix of old and new values.\n        # Anyway, flushing the FTDI-to-host buffer seems to be a proper\n        # to get in sync with the buffer.\n        if noflush:\n            return self._ftdi.read_data(readlen)\n        loop = 10000\n        while loop:\n            loop -= 1\n            # do not attempt to do anything till the FTDI HW buffer has been\n            # emptied, i.e. previous write calls have been handled.\n            status = self._ftdi.poll_modem_status()\n            if status & Ftdi.MODEM_TEMT:\n                # TX buffer is now empty, any \"write\" GPIO rquest has completed\n                # so start reading GPIO samples from this very moment.\n                break\n        else:\n            # sanity check to avoid endless loop on errors\n            raise FtdiError('FTDI TX buffer error')\n        # now flush the FTDI-to-host buffer as it keeps being filled with data\n        self._ftdi.purge_tx_buffer()\n        # finally perform the actual read out\n        return self._ftdi.read_data(readlen)\n\n    def write(self, out: Union[bytes, bytearray, int]) -> None:\n        \"\"\"Set the GPIO output pin electrical level, or output a sequence of\n           bytes @ constant frequency to GPIO output pins.\n\n           :param out: a bitfield of GPIO pins, or a sequence of them\n        \"\"\"\n        if not self.is_connected:\n            raise GpioException('Not connected')\n        if isinstance(out, (bytes, bytearray)):\n            pass\n        else:\n            if isinstance(out, int):\n                out = bytes([out])\n            else:\n                if not is_iterable(out):\n                    raise TypeError('Invalid output value')\n            for val in out:\n                if val > self._mask:\n                    raise ValueError('Invalid output value')\n            out = bytes(out)\n        self._ftdi.write_data(out)\n\n    def set_frequency(self, frequency: Union[int, float]) -> None:\n        \"\"\"Set the frequency at which sequence of GPIO samples are read\n           and written.\n\n           note: FTDI may update its clock register before it has emptied its\n           internal buffer. If the current frequency is \"low\", some\n           yet-to-output bytes may end up being clocked at the new frequency.\n\n           Unfortunately, it seems there is no way to wait for the internal\n           buffer to be emptied out. They can be flushed (i.e. discarded), but\n           not synchronized :-(\n\n           PyFtdi client should add \"some\" short delay to ensure a previous,\n           long write request has been fully output @ low freq before changing\n           the frequency.\n\n           Beware that only some exact frequencies can be generated. Contrary\n           to the UART mode, an approximate frequency is always accepted for\n           GPIO/bitbang mode. To get the actual frequency, and optionally abort\n           if it is out-of-spec, use :py:meth:`frequency` property.\n\n           :param frequency: the new frequency, in GPIO samples per second\n        \"\"\"\n        self._frequency = float(self._ftdi.set_baudrate(int(frequency), False))\n\n    def _configure(self, url: str, direction: int,\n                   frequency: Union[int, float, None] = None, **kwargs) -> int:\n        if 'initial' in kwargs:\n            initial = kwargs['initial']\n            del kwargs['initial']\n        else:\n            initial = None\n        if 'debug' in kwargs:\n            # debug is not implemented\n            del kwargs['debug']\n        baudrate = int(frequency) if frequency is not None else None\n        baudrate = self._ftdi.open_bitbang_from_url(url,\n                                                    direction=direction,\n                                                    sync=False,\n                                                    baudrate=baudrate,\n                                                    **kwargs)\n        self._width = 8\n        self._mask = (1 << self._width) - 1\n        self._direction = direction & self._mask\n        if initial is not None:\n            initial &= self._mask\n            self.write(initial)\n        return float(baudrate)\n\n    def _update_direction(self) -> None:\n        self._ftdi.set_bitmode(self._direction, Ftdi.BitMode.BITBANG)\n\n    # old API names\n    open_from_url = GpioBaseController.configure\n    read_port = read\n    write_port = write\n\n\n# old API compatibility\nGpioController = GpioAsyncController\n\n\nclass GpioSyncController(GpioBaseController):\n    \"\"\"GPIO controller for an FTDI port, in bit-bang synchronous mode.\n\n       GPIO accessible pins are limited to the 8 lower pins of each GPIO port.\n\n       Synchronous bitbang input and output are synchronized. Eveery time GPIO\n       output is updated, the GPIO input is sampled and buffered.\n\n       Update and sampling are clocked at the selected frequency. The GPIO\n       samples are transfer in both direction with the :py:meth:`exchange`\n       method, which therefore always returns as many input samples as output\n       bytes.\n\n       Note that FTDI internal clock divider cannot generate any arbitrary\n       frequency, so the closest frequency to the request one that can be\n       generated is selected. The actual :py:attr:`frequency` may be tested to\n       check if it matches the board requirements.\n    \"\"\"\n\n    def exchange(self, out: Union[bytes, bytearray]) -> bytes:\n        \"\"\"Set the GPIO output pin electrical level, or output a sequence of\n           bytes @ constant frequency to GPIO output pins.\n\n           :param out: the byte buffer to output as GPIO\n           :return: a byte buffer of the same length as out buffer.\n        \"\"\"\n        if not self.is_connected:\n            raise GpioException('Not connected')\n        if isinstance(out, (bytes, bytearray)):\n            pass\n        else:\n            if isinstance(out, int):\n                out = bytes([out])\n            elif not is_iterable(out):\n                raise TypeError('Invalid output value')\n            for val in out:\n                if val > self._mask:\n                    raise GpioException(\"Invalid value\")\n        self._ftdi.write_data(out)\n        data = self._ftdi.read_data_bytes(len(out), 4)\n        return data\n\n    def set_frequency(self, frequency: Union[int, float]) -> None:\n        \"\"\"Set the frequency at which sequence of GPIO samples are read\n           and written.\n\n           :param frequency: the new frequency, in GPIO samples per second\n        \"\"\"\n        self._frequency = float(self._ftdi.set_baudrate(int(frequency), False))\n\n    def _configure(self, url: str, direction: int,\n                   frequency: Union[int, float, None] = None, **kwargs):\n        if 'initial' in kwargs:\n            initial = kwargs['initial']\n            del kwargs['initial']\n        else:\n            initial = None\n        if 'debug' in kwargs:\n            # debug is not implemented\n            del kwargs['debug']\n        baudrate = int(frequency) if frequency is not None else None\n        baudrate = self._ftdi.open_bitbang_from_url(url,\n                                                    direction=direction,\n                                                    sync=True,\n                                                    baudrate=baudrate,\n                                                    **kwargs)\n        self._width = 8\n        self._mask = (1 << self._width) - 1\n        self._direction = direction & self._mask\n        if initial is not None:\n            initial &= self._mask\n            self.exchange(initial)\n        return float(baudrate)\n\n    def _update_direction(self) -> None:\n        self._ftdi.set_bitmode(self._direction, Ftdi.BitMode.SYNCBB)\n\n\nclass GpioMpsseController(GpioBaseController):\n    \"\"\"GPIO controller for an FTDI port, in MPSSE mode.\n\n       All GPIO pins are reachable, but MPSSE mode is slower than other modes.\n\n       Beware that LSBs (b0..b7) and MSBs (b8..b15) are accessed with two\n       subsequence commands, so a slight delay may occur when sampling or\n       changing both groups at once. In other word, it is not possible to\n       atomically read to / write from LSBs and MSBs. This might be worth\n       checking the board design if atomic access to several lines is required.\n    \"\"\"\n\n    MPSSE_PAYLOAD_MAX_LENGTH = 0xFF00  # 16 bits max (- spare for control)\n\n    def read(self, readlen: int = 1, peek: Optional[bool] = None) \\\n            -> Union[int, bytes, Tuple[int]]:\n        \"\"\"Read the GPIO input pin electrical level.\n\n           :param readlen: how many GPIO samples to retrieve. Each sample if\n                           :py:meth:`width` bit wide.\n           :param peek: whether to peak current value from port, or to use\n                        MPSSE stream and HW FIFO. When peek mode is selected,\n                        readlen should be 1. It is not available with wide\n                        ports if some of the MSB pins are configured as input\n           :return: a :py:meth:`width` bit wide integer if direct mode is used,\n                    a bytes buffer if :py:meth:`width` is a byte,\n                    a list of integer otherwise (MPSSE mode only).\n        \"\"\"\n        if not self.is_connected:\n            raise GpioException('Not connected')\n        if peek:\n            if readlen != 1:\n                raise ValueError('Invalid read length with direct mode')\n            if self._width > 8:\n                if (0xFFFF & ~self._direction) >> 8:\n                    raise ValueError('Peek mode not available with selected '\n                                     'input config')\n        if peek:\n            return self._ftdi.read_pins()\n        return self._read_mpsse(readlen)\n\n    def write(self, out: Union[bytes, bytearray, Iterable[int], int]) -> None:\n        \"\"\"Set the GPIO output pin electrical level, or output a sequence of\n           bytes @ constant frequency to GPIO output pins.\n\n           :param out: a bitfield of GPIO pins, or a sequence of them\n        \"\"\"\n        if not self.is_connected:\n            raise GpioException('Not connected')\n        if isinstance(out, (bytes, bytearray)):\n            pass\n        else:\n            if isinstance(out, int):\n                out = [out]\n            elif not is_iterable(out):\n                raise TypeError('Invalid output value')\n            for val in out:\n                if val > self._mask:\n                    raise GpioException(\"Invalid value\")\n        self._write_mpsse(out)\n\n    def set_frequency(self, frequency: Union[int, float]) -> None:\n        if not self.is_connected:\n            raise GpioException('Not connected')\n        self._frequency = self._ftdi.set_frequency(float(frequency))\n\n    def _update_direction(self) -> None:\n        # nothing to do in MPSSE mode, as direction is updated with each\n        # GPIO command\n        pass\n\n    def _configure(self, url: str, direction: int,\n                   frequency: Union[int, float, None] = None, **kwargs):\n        frequency = self._ftdi.open_mpsse_from_url(url,\n                                                   direction=direction,\n                                                   frequency=frequency,\n                                                   **kwargs)\n        self._width = self._ftdi.port_width\n        self._mask = (1 << self._width) - 1\n        self._direction = direction & self._mask\n        return frequency\n\n    def _read_mpsse(self, count: int) -> Tuple[int]:\n        if self._width > 8:\n            cmd = bytearray([Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH] * count)\n            fmt = f'<{count}H'\n        else:\n            cmd = bytearray([Ftdi.GET_BITS_LOW] * count)\n            fmt = None\n        cmd.append(Ftdi.SEND_IMMEDIATE)\n        if len(cmd) > self.MPSSE_PAYLOAD_MAX_LENGTH:\n            raise ValueError('Too many samples')\n        self._ftdi.write_data(cmd)\n        size = scalc(fmt) if fmt else count\n        data = self._ftdi.read_data_bytes(size, 4)\n        if len(data) != size:\n            raise FtdiError(f'Cannot read GPIO, recv {len(data)} '\n                            f'out of {size} bytes')\n        if fmt:\n            return sunpack(fmt, data)\n        return data\n\n    def _write_mpsse(self,\n                     out: Union[bytes, bytearray, Iterable[int], int]) -> None:\n        cmd = []\n        low_dir = self._direction & 0xFF\n        if self._width > 8:\n            high_dir = (self._direction >> 8) & 0xFF\n            for data in out:\n                low_data = data & 0xFF\n                high_data = (data >> 8) & 0xFF\n                cmd.extend([Ftdi.SET_BITS_LOW, low_data, low_dir,\n                            Ftdi.SET_BITS_HIGH, high_data, high_dir])\n        else:\n            for data in out:\n                cmd.extend([Ftdi.SET_BITS_LOW, data, low_dir])\n        self._ftdi.write_data(bytes(cmd))\n"
  },
  {
    "path": "pyftdi/i2c.py",
    "content": "# Copyright (c) 2017-2025, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"I2C support for PyFdti\"\"\"\n\nfrom binascii import hexlify\nfrom collections import namedtuple\nfrom logging import getLogger\nfrom struct import calcsize as scalc, pack as spack, unpack as sunpack\nfrom threading import Lock\nfrom typing import Any, Iterable, Mapping, Optional, Tuple, Union\nfrom usb.core import Device as UsbDevice\nfrom .ftdi import Ftdi, FtdiFeatureError\nfrom .misc import to_bool\n\n\nclass I2cIOError(IOError):\n    \"\"\"I2c I/O error\"\"\"\n\n\nclass I2cNackError(I2cIOError):\n    \"\"\"I2c NACK receive from slave\"\"\"\n\n\nclass I2cTimeoutError(TimeoutError):\n    \"\"\"I2c timeout on polling\"\"\"\n\n\nclass I2cPort:\n    \"\"\"I2C port.\n\n       An I2C port is never instanciated directly:\n       use :py:meth:`I2cController.get_port()` method to obtain an I2C port.\n\n       ``relax`` parameter in I2cPort methods may be used to prevent the master\n       from releasing the I2C bus, if some further data should be exchanged\n       with the slave device. Note that in case of any error, the I2C bus is\n       released and the ``relax`` parameter is ignored in such an event.\n\n       Example:\n\n       >>> ctrl = I2cController()\n       >>> ctrl.configure('ftdi://ftdi:232h/1')\n       >>> i2c = ctrl.get_port(0x21)\n       >>> # send 2 bytes\n       >>> i2c.write([0x12, 0x34])\n       >>> # send 2 bytes, then receive 2 bytes\n       >>> out = i2c.exchange([0x12, 0x34], 2)\n    \"\"\"\n    FORMATS = {scalc(fmt): fmt for fmt in 'BHI'}\n\n    def __init__(self, controller: 'I2cController', address: int):\n        self._controller = controller\n        self._address = address\n        self._shift = 0\n        self._endian = '<'\n        self._format = 'B'\n\n    def configure_register(self,\n                           bigendian: bool = False, width: int = 1) -> None:\n        \"\"\"Reconfigure the format of the slave address register (if any)\n\n            :param bigendian: True for a big endian encoding, False otherwise\n            :param width: width, in bytes, of the register\n        \"\"\"\n        try:\n            self._format = self.FORMATS[width]\n        except KeyError as exc:\n            raise I2cIOError('Unsupported integer width') from exc\n        self._endian = '>' if bigendian else '<'\n\n    def shift_address(self, offset: int):\n        \"\"\"Tweak the I2C slave address, as required with some devices\n        \"\"\"\n        I2cController.validate_address(self._address+offset)\n        self._shift = offset\n\n    def read(self, readlen: int = 0, relax: bool = True,\n             start: bool = True) -> bytes:\n        \"\"\"Read one or more bytes from a remote slave\n\n           :param readlen: count of bytes to read out.\n           :param relax: whether to relax the bus (emit STOP) or not\n           :param start: whether to emit a start sequence (w/ address)\n           :return: byte sequence of read out bytes\n           :raise I2cIOError: if device is not configured or input parameters\n                              are invalid\n        \"\"\"\n        return self._controller.read(\n            self._address+self._shift if start else None,\n            readlen=readlen, relax=relax)\n\n    def write(self, out: Union[bytes, bytearray, Iterable[int]],\n              relax: bool = True, start: bool = True) -> None:\n        \"\"\"Write one or more bytes to a remote slave\n\n           :param out: the byte buffer to send\n           :param relax: whether to relax the bus (emit STOP) or not\n           :param start: whether to emit a start sequence (w/ address)\n           :raise I2cIOError: if device is not configured or input parameters\n                              are invalid\n        \"\"\"\n        return self._controller.write(\n            self._address+self._shift if start else None,\n            out, relax=relax)\n\n    def read_from(self, regaddr: int, readlen: int = 0,\n                  relax: bool = True, start: bool = True) -> bytes:\n        \"\"\"Read one or more bytes from a given register at remote slave\n\n           :param regaddr: slave register address to read from\n           :param readlen: count of bytes to read out.\n           :param relax: whether to relax the bus (emit STOP) or not\n           :param start: whether to emit a start sequence (w/ address)\n           :return: data read out from the slave\n           :raise I2cIOError: if device is not configured or input parameters\n                              are invalid\n        \"\"\"\n        return self._controller.exchange(\n            self._address+self._shift if start else None,\n            out=self._make_buffer(regaddr), readlen=readlen, relax=relax)\n\n    def write_to(self, regaddr: int,\n                 out: Union[bytes, bytearray, Iterable[int]],\n                 relax: bool = True, start: bool = True):\n        \"\"\"Write one or more bytes to a given register at a remote slave\n\n           :param regaddr: slave register address to write to\n           :param out: the byte buffer to send\n           :param relax: whether to relax the bus (emit STOP) or not\n           :param start: whether to emit a start sequence (w/ address)\n           :raise I2cIOError: if device is not configured or input parameters\n                              are invalid\n        \"\"\"\n        return self._controller.write(\n            self._address+self._shift if start else None,\n            out=self._make_buffer(regaddr, out), relax=relax)\n\n    def exchange(self, out: Union[bytes, bytearray, Iterable[int]] = b'',\n                 readlen: int = 0,\n                 relax: bool = True, start: bool = True) -> bytes:\n        \"\"\"Perform an exchange or a transaction with the I2c slave\n\n           :param out: an array of bytes to send to the I2c slave,\n                       may be empty to only read out data from the slave\n           :param readlen: count of bytes to read out from the slave,\n                       may be zero to only write to the slave\n           :param relax: whether to relax the bus (emit STOP) or not\n           :param start: whether to emit a start sequence (w/ address)\n           :return: data read out from the slave\n        \"\"\"\n        return self._controller.exchange(\n            self._address+self._shift if start else None, out,\n            readlen, relax=relax)\n\n    def poll(self, write: bool = False,\n             relax: bool = True, start: bool = True) -> bool:\n        \"\"\"Poll a remote slave, expect ACK or NACK.\n\n           :param write: poll in write mode (vs. read)\n           :param relax: whether to relax the bus (emit STOP) or not\n           :param start: whether to emit a start sequence (w/ address)\n           :return: True if the slave acknowledged, False otherwise\n        \"\"\"\n        return self._controller.poll(\n            self._address+self._shift if start else None, write,\n            relax=relax)\n\n    def poll_cond(self, width: int, mask: int, value: int, count: int,\n                  relax: bool = True, start: bool = True) -> Optional[bytes]:\n        \"\"\"Poll a remove slave, watching for condition to satisfy.\n           On each poll cycle, a repeated start condition is emitted, without\n           releasing the I2C bus, and an ACK is returned to the slave.\n\n           If relax is set, this method releases the I2C bus however it leaves.\n\n           :param width: count of bytes to poll for the condition check,\n                that is the size of the condition register\n           :param mask: binary mask to apply on the condition register\n                before testing for the value\n           :param value: value to test the masked condition register\n                against. Condition is satisfied when register & mask == value\n           :param count: maximum poll count before raising a timeout\n           :param relax: whether to relax the bus (emit STOP) or not\n           :param start: whether to emit a start sequence (w/ address)\n           :return: the polled register value\n           :raise I2cTimeoutError: if poll condition is not satisified\n        \"\"\"\n        try:\n            fmt = ''.join((self._endian, self.FORMATS[width]))\n        except KeyError as exc:\n            raise I2cIOError('Unsupported integer width') from exc\n        return self._controller.poll_cond(\n            self._address+self._shift if start else None,\n            fmt, mask, value, count, relax=relax)\n\n    def flush(self) -> None:\n        \"\"\"Force the flush of the HW FIFOs.\n        \"\"\"\n        self._controller.flush()\n\n    @property\n    def frequency(self) -> float:\n        \"\"\"Provide the current I2c bus frequency.\n        \"\"\"\n        return self._controller.frequency\n\n    @property\n    def address(self) -> int:\n        \"\"\"Return the slave address.\"\"\"\n        return self._address\n\n    def _make_buffer(self, regaddr: int,\n                     out: Union[bytes, bytearray, Iterable[int],\n                                None] = None) -> bytes:\n        data = bytearray()\n        data.extend(spack(f'{self._endian}{self._format}', regaddr))\n        if out:\n            data.extend(out)\n        return bytes(data)\n\n\nclass I2cGpioPort:\n    \"\"\"GPIO port\n\n       A I2cGpioPort instance enables to drive GPIOs wich are not reserved for\n       I2c feature as regular GPIOs.\n\n       GPIO are managed as a bitfield. The LSBs are reserved for the I2c\n       feature, which means that the lowest pin that can be used as a GPIO is\n       *b3*:\n\n       * *b0*: I2C SCL\n       * *b1*: I2C SDA_O\n       * *b2*: I2C SDA_I\n       * *b3*: first GPIO\n       * *b7*: reserved for I2C clock stretching, if this mode is enabled\n\n       There is no offset bias in GPIO bit position, *i.e.* the first available\n       GPIO can be reached from as ``0x08``.\n\n       Bitfield size depends on the FTDI device: 4432H series use 8-bit GPIO\n       ports, while 232H and 2232H series use wide 16-bit ports.\n\n       An I2cGpio port is never instanciated directly: use\n       :py:meth:`I2cController.get_gpio()` method to obtain the GPIO port.\n    \"\"\"\n    def __init__(self, controller: 'I2cController'):\n        self.log = getLogger('pyftdi.i2c.gpio')\n        self._controller = controller\n\n    @property\n    def pins(self) -> int:\n        \"\"\"Report the configured GPIOs as a bitfield.\n\n           A true bit represents a GPIO, a false bit a reserved or not\n           configured pin.\n\n           :return: the bitfield of configured GPIO pins.\n        \"\"\"\n        return self._controller.gpio_pins\n\n    @property\n    def all_pins(self) -> int:\n        \"\"\"Report the addressable GPIOs as a bitfield.\n\n           A true bit represents a pin which may be used as a GPIO, a false bit\n           a reserved pin (for I2C support)\n\n           :return: the bitfield of configurable GPIO pins.\n        \"\"\"\n        return self._controller.gpio_all_pins\n\n    @property\n    def width(self) -> int:\n        \"\"\"Report the FTDI count of addressable pins.\n\n           Note that all pins, including reserved I2C ones, are reported.\n\n           :return: the count of IO pins (including I2C ones).\n        \"\"\"\n        return self._controller.width\n\n    @property\n    def direction(self) -> int:\n        \"\"\"Provide the FTDI GPIO direction.self\n\n           A true bit represents an output GPIO, a false bit an input GPIO.\n\n           :return: the bitfield of direction.\n        \"\"\"\n        return self._controller.direction\n\n    def read(self, with_output: bool = False) -> int:\n        \"\"\"Read GPIO port.\n\n           :param with_output: set to unmask output pins\n           :return: the GPIO port pins as a bitfield\n        \"\"\"\n        return self._controller.read_gpio(with_output)\n\n    def write(self, value: int) -> None:\n        \"\"\"Write GPIO port.\n\n           :param value: the GPIO port pins as a bitfield\n        \"\"\"\n        return self._controller.write_gpio(value)\n\n    def set_direction(self, pins: int, direction: int) -> None:\n        \"\"\"Change the direction of the GPIO pins.\n\n           :param pins: which GPIO pins should be reconfigured\n           :param direction: direction bitfield (high level for output)\n        \"\"\"\n        self._controller.set_gpio_direction(pins, direction)\n\n\nI2CTimings = namedtuple('I2CTimings', 't_hd_sta t_su_sta t_su_sto t_buf')\n\"\"\"I2C standard timings.\n\"\"\"\n\n\nclass I2cController:\n    \"\"\"I2c master.\n\n       An I2c master should be instanciated only once for each FTDI port that\n       supports MPSSE (one or two ports, depending on the FTDI device).\n\n       Once configured, :py:func:`get_port` should be invoked to obtain an I2c\n       port for each I2c slave to drive. I2c port should handle all I/O\n       requests for its associated HW slave.\n\n       It is not recommended to use I2cController :py:func:`read`,\n       :py:func:`write` or :py:func:`exchange` directly.\n\n       * ``SCK`` should be connected to ``A*BUS0``, and ``A*BUS7`` if clock\n         stretching mode is enabled\n       * ``SDA`` should be connected to ``A*BUS1`` **and** ``A*BUS2``\n    \"\"\"\n\n    LOW = 0x00\n    HIGH = 0xff\n    BIT0 = 0x01\n    IDLE = HIGH\n    SCL_BIT = 0x01  # AD0\n    SDA_O_BIT = 0x02  # AD1\n    SDA_I_BIT = 0x04  # AD2\n    SCL_FB_BIT = 0x80  # AD7\n    PAYLOAD_MAX_LENGTH = 0xFF00  # 16 bits max (- spare for control)\n    HIGHEST_I2C_ADDRESS = 0x7F\n    DEFAULT_BUS_FREQUENCY = 100000.0\n    HIGH_BUS_FREQUENCY = 400000.0\n    RETRY_COUNT = 3\n\n    I2C_MASK = SCL_BIT | SDA_O_BIT | SDA_I_BIT\n    I2C_MASK_CS = SCL_BIT | SDA_O_BIT | SDA_I_BIT | SCL_FB_BIT\n    I2C_DIR = SCL_BIT | SDA_O_BIT\n\n    I2C_100K = I2CTimings(4.0E-6, 4.7E-6, 4.0E-6, 4.7E-6)\n    I2C_400K = I2CTimings(0.6E-6, 0.6E-6, 0.6E-6, 1.3E-6)\n    I2C_1M = I2CTimings(0.26E-6, 0.26E-6, 0.26E-6, 0.5E-6)\n\n    def __init__(self):\n        self._ftdi = Ftdi()\n        self._lock = Lock()\n        self.log = getLogger('pyftdi.i2c')\n        self._gpio_port = None\n        self._gpio_dir = 0\n        self._gpio_low = 0\n        self._gpio_mask = 0\n        self._i2c_mask = 0\n        self._wide_port = False\n        self._slaves = {}\n        self._retry_count = self.RETRY_COUNT\n        self._frequency = 0.0\n        self._immediate = (Ftdi.SEND_IMMEDIATE,)\n        self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0)\n        self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0)\n        self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0)\n        self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH)\n        self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW)\n        self._ck_delay = 1\n        self._fake_tristate = False\n        self._tx_size = 1\n        self._rx_size = 1\n        self._ck_hd_sta = 0\n        self._ck_su_sto = 0\n        self._ck_idle = 0\n        self._read_optim = True\n        self._disable_3phase_clock = False\n        self._clkstrch = False\n\n    def set_retry_count(self, count: int) -> None:\n        \"\"\"Change the default retry count when a communication error occurs,\n           before bailing out.\n           :param count: count of retries\n        \"\"\"\n        if not isinstance(count, int) or not 0 < count <= 16:\n            raise ValueError('Invalid retry count')\n        self._retry_count = count\n\n    def configure(self, url: Union[str, UsbDevice],\n                  **kwargs: Mapping[str, Any]) -> None:\n        \"\"\"Configure the FTDI interface as a I2c master.\n\n           :param url: FTDI URL string, such as ``ftdi://ftdi:232h/1``\n           :param kwargs: options to configure the I2C bus\n\n           Accepted options:\n\n           * ``interface``: when URL is specifed as a USB device, the interface\n             named argument can be used to select a specific port of the FTDI\n             device, as an integer starting from 1.\n           * ``direction`` a bitfield specifying the FTDI GPIO direction,\n             where high level defines an output, and low level defines an\n             input. Only useful to setup default IOs at start up, use\n             :py:class:`I2cGpioPort` to drive GPIOs. Note that pins reserved\n             for I2C feature take precedence over any this setting.\n           * ``initial`` a bitfield specifying the initial output value. Only\n             useful to setup default IOs at start up, use\n             :py:class:`I2cGpioPort` to drive GPIOs.\n           * ``frequency`` float value the I2C bus frequency in Hz\n           * ``clockstretching`` boolean value to enable clockstreching.\n             xD7 (GPIO7) pin should be connected back to xD0 (SCK)\n           * ``debug`` to increase log verbosity, using MPSSE tracer\n        \"\"\"\n        if 'frequency' in kwargs:\n            frequency = kwargs['frequency']\n            del kwargs['frequency']\n        else:\n            frequency = self.DEFAULT_BUS_FREQUENCY\n        # Fix frequency for 3-phase clock\n        if frequency <= 100E3:\n            timings = self.I2C_100K\n        elif frequency <= 400E3:\n            timings = self.I2C_400K\n        else:\n            timings = self.I2C_1M\n        if 'clockstretching' in kwargs:\n            self._clkstrch = bool(kwargs['clockstretching'])\n            del kwargs['clockstretching']\n        else:\n            self._clkstrch = False\n        if 'direction' in kwargs:\n            io_dir = int(kwargs['direction'])\n            del kwargs['direction']\n        else:\n            io_dir = 0\n        if 'initial' in kwargs:\n            io_out = int(kwargs['initial'])\n            del kwargs['initial']\n        else:\n            io_out = 0\n        if 'interface' in kwargs:\n            if isinstance(url, str):\n                raise I2cIOError('url and interface are mutually exclusive')\n            interface = int(kwargs['interface'])\n            del kwargs['interface']\n        else:\n            interface = 1\n        if 'rdoptim' in kwargs:\n            self._read_optim = to_bool(kwargs['rdoptim'])\n            del kwargs['rdoptim']\n        with self._lock:\n            self._ck_hd_sta = self._compute_delay_cycles(timings.t_hd_sta)\n            self._ck_su_sto = self._compute_delay_cycles(timings.t_su_sto)\n            ck_su_sta = self._compute_delay_cycles(timings.t_su_sta)\n            ck_buf = self._compute_delay_cycles(timings.t_buf)\n            self._ck_idle = max(ck_su_sta, ck_buf)\n            self._ck_delay = ck_buf\n            if self._clkstrch:\n                self._i2c_mask = self.I2C_MASK_CS\n            else:\n                self._i2c_mask = self.I2C_MASK\n            # until the device is open, there is no way to tell if it has a\n            # wide (16) or narrow port (8). Lower API can deal with any, so\n            # delay any truncation till the device is actually open\n            self._set_gpio_direction(16, io_out, io_dir)\n            # as 3-phase clock frequency mode is required for I2C mode, the\n            # FTDI clock should be adapted to match the required frequency.\n            kwargs['direction'] = self.I2C_DIR | self._gpio_dir\n            kwargs['initial'] = self.IDLE | (io_out & self._gpio_mask)\n            kwargs['frequency'] = (3.0*frequency)/2.0\n            if not isinstance(url, str):\n                frequency = self._ftdi.open_mpsse_from_device(\n                    url, interface=interface, **kwargs)\n            else:\n                frequency = self._ftdi.open_mpsse_from_url(url, **kwargs)\n            self._frequency = (2.0*frequency)/3.0\n            self._tx_size, self._rx_size = self._ftdi.fifo_sizes\n            if not self._disable_3phase_clock:\n                self._ftdi.enable_3phase_clock(True)\n            try:\n                self._ftdi.enable_drivezero_mode(self.SCL_BIT |\n                                                 self.SDA_O_BIT |\n                                                 self.SDA_I_BIT)\n            except FtdiFeatureError:\n                # when open collector feature is not available (FT2232, FT4232)\n                # SDA line is temporary move to high-z to enable ACK/NACK\n                # read back from slave\n                self._fake_tristate = True\n            self._wide_port = self._ftdi.has_wide_port\n            if not self._wide_port:\n                self._set_gpio_direction(8, io_out & 0xFF, io_dir & 0xFF)\n\n    def force_clock_mode(self, enable: bool) -> None:\n        \"\"\"Force unsupported I2C clock signalling on devices that have no I2C\n           capabilities (i.e. FT2232D). I2cController cowardly refuses to use\n           unsupported devices. When this mode is enabled, I2cController can\n           drive such devices, but I2C signalling is not compliant with I2C\n           specifications and may not work with most I2C slaves.\n\n           :py:meth:`force_clock_mode` should always be called before\n           :py:meth:`configure` to be effective.\n\n           This is a fully unsupported feature (bug reports will be ignored).\n\n           :param enable: whether to drive non-I2C capable devices.\n        \"\"\"\n        if enable:\n            self.log.info('I2C signalling forced to non-I2C compliant mode.')\n        self._disable_3phase_clock = enable\n\n    def close(self, freeze: bool = False) -> None:\n        \"\"\"Close the FTDI interface.\n\n           :param freeze: if set, FTDI port is not reset to its default\n                          state on close.\n        \"\"\"\n        with self._lock:\n            if self._ftdi.is_connected:\n                self._ftdi.close(freeze)\n\n    def terminate(self) -> None:\n        \"\"\"Close the FTDI interface.\n\n           :note: deprecated API, use close()\n        \"\"\"\n        self.close()\n\n    def get_port(self, address: int) -> I2cPort:\n        \"\"\"Obtain an I2cPort to drive an I2c slave.\n\n           :param address: the address on the I2C bus\n           :return: an I2cPort instance\n        \"\"\"\n        if not self._ftdi.is_connected:\n            raise I2cIOError(\"FTDI controller not initialized\")\n        self.validate_address(address)\n        if address not in self._slaves:\n            self._slaves[address] = I2cPort(self, address)\n        return self._slaves[address]\n\n    def get_gpio(self) -> I2cGpioPort:\n        \"\"\"Retrieve the GPIO port.\n\n           :return: GPIO port\n        \"\"\"\n        with self._lock:\n            if not self._ftdi.is_connected:\n                raise I2cIOError(\"FTDI controller not initialized\")\n            if not self._gpio_port:\n                self._gpio_port = I2cGpioPort(self)\n            return self._gpio_port\n\n    @property\n    def ftdi(self) -> Ftdi:\n        \"\"\"Return the Ftdi instance.\n\n           :return: the Ftdi instance\n        \"\"\"\n        return self._ftdi\n\n    @property\n    def configured(self) -> bool:\n        \"\"\"Test whether the device has been properly configured.\n\n           :return: True if configured\n        \"\"\"\n        return self._ftdi.is_connected and bool(self._start)\n\n    @classmethod\n    def validate_address(cls, address: Optional[int]) -> None:\n        \"\"\"Assert an I2C slave address is in the supported range.\n           None is a special bypass address.\n\n           :param address: the address on the I2C bus\n           :raise I2cIOError: if the I2C slave address is not supported\n        \"\"\"\n        if address is None:\n            return\n        if address > cls.HIGHEST_I2C_ADDRESS:\n            raise I2cIOError(f'No such I2c slave: 0x{address:02x}')\n\n    @property\n    def frequency_max(self) -> float:\n        \"\"\"Provides the maximum I2C clock frequency in Hz.\n\n           :return: I2C bus clock frequency\n        \"\"\"\n        return self._ftdi.frequency_max\n\n    @property\n    def frequency(self) -> float:\n        \"\"\"Provides the current I2C clock frequency in Hz.\n\n           :return: the I2C bus clock frequency\n        \"\"\"\n        return self._frequency\n\n    @property\n    def direction(self) -> int:\n        \"\"\"Provide the FTDI pin direction\n\n           A true bit represents an output pin, a false bit an input pin.\n\n           :return: the bitfield of direction.\n        \"\"\"\n        return self.I2C_DIR | self._gpio_dir\n\n    @property\n    def gpio_pins(self) -> int:\n        \"\"\"Report the configured GPIOs as a bitfield.\n\n           A true bit represents a GPIO, a false bit a reserved or not\n           configured pin.\n\n           :return: the bitfield of configured GPIO pins.\n        \"\"\"\n        with self._lock:\n            return self._gpio_mask\n\n    @property\n    def gpio_all_pins(self) -> int:\n        \"\"\"Report the addressable GPIOs as a bitfield.\n\n           A true bit represents a pin which may be used as a GPIO, a false bit\n           a reserved pin (for I2C support)\n\n           :return: the bitfield of configurable GPIO pins.\n        \"\"\"\n        mask = (1 << self.width) - 1\n        with self._lock:\n            return mask & ~self._i2c_mask\n\n    @property\n    def width(self) -> int:\n        \"\"\"Report the FTDI count of addressable pins.\n\n           :return: the count of IO pins (including I2C ones).\n        \"\"\"\n        return 16 if self._wide_port else 8\n\n    def read(self, address: Optional[int], readlen: int = 1,\n             relax: bool = True) -> bytes:\n        \"\"\"Read one or more bytes from a remote slave\n\n           :param address: the address on the I2C bus, or None to discard start\n           :param readlen: count of bytes to read out.\n           :param relax: not used\n           :return: read bytes\n           :raise I2cIOError: if device is not configured or input parameters\n                              are invalid\n\n           Address is a logical slave address (0x7f max)\n\n           Most I2C devices require a register address to read out\n           check out the exchange() method.\n        \"\"\"\n        if not self.configured:\n            raise I2cIOError(\"FTDI controller not initialized\")\n        self.validate_address(address)\n        if address is None:\n            i2caddress = None\n        else:\n            i2caddress = (address << 1) & self.HIGH\n            i2caddress |= self.BIT0\n        retries = self._retry_count\n        do_epilog = True\n        with self._lock:\n            while True:\n                try:\n                    self._do_prolog(i2caddress)\n                    data = self._do_read(readlen)\n                    do_epilog = relax\n                    return data\n                except I2cNackError:\n                    retries -= 1\n                    if not retries:\n                        raise\n                    self.log.warning('Retry read')\n                finally:\n                    if do_epilog:\n                        self._do_epilog()\n\n    def write(self, address: Optional[int],\n              out: Union[bytes, bytearray, Iterable[int]],\n              relax: bool = True) -> None:\n        \"\"\"Write one or more bytes to a remote slave\n\n           :param address: the address on the I2C bus, or None to discard start\n           :param out: the byte buffer to send\n           :param relax: whether to relax the bus (emit STOP) or not\n           :raise I2cIOError: if device is not configured or input parameters\n                              are invalid\n\n           Address is a logical slave address (0x7f max)\n\n           Most I2C devices require a register address to write into. It should\n           be added as the first (byte)s of the output buffer.\n        \"\"\"\n        if not self.configured:\n            raise I2cIOError(\"FTDI controller not initialized\")\n        self.validate_address(address)\n        if address is None:\n            i2caddress = None\n        else:\n            i2caddress = (address << 1) & self.HIGH\n        retries = self._retry_count\n        do_epilog = True\n        with self._lock:\n            while True:\n                try:\n                    self._do_prolog(i2caddress)\n                    self._do_write(out)\n                    do_epilog = relax\n                    return\n                except I2cNackError:\n                    retries -= 1\n                    if not retries:\n                        raise\n                    self.log.warning('Retry write')\n                finally:\n                    if do_epilog:\n                        self._do_epilog()\n\n    def exchange(self, address: Optional[int],\n                 out: Union[bytes, bytearray, Iterable[int]],\n                 readlen: int = 0, relax: bool = True) -> bytearray:\n        \"\"\"Send a byte sequence to a remote slave followed with\n           a read request of one or more bytes.\n\n           This command is useful to tell the slave what data\n           should be read out.\n\n           :param address: the address on the I2C bus, or None to discard start\n           :param out: the byte buffer to send\n           :param readlen: count of bytes to read out.\n           :param relax: whether to relax the bus (emit STOP) or not\n           :return: read bytes\n           :raise I2cIOError: if device is not configured or input parameters\n                              are invalid\n\n           Address is a logical slave address (0x7f max)\n        \"\"\"\n        if not self.configured:\n            raise I2cIOError(\"FTDI controller not initialized\")\n        self.validate_address(address)\n        if readlen < 1:\n            raise I2cIOError('Nothing to read')\n        if readlen > (self.PAYLOAD_MAX_LENGTH/3-1):\n            raise I2cIOError(\"Input payload is too large\")\n        if address is None:\n            i2caddress = None\n        else:\n            i2caddress = (address << 1) & self.HIGH\n        retries = self._retry_count\n        do_epilog = True\n        data = bytearray()\n        with self._lock:\n            while True:\n                try:\n                    self._do_prolog(i2caddress)\n                    self._do_write(out)\n                    if i2caddress is not None:\n                        self._do_prolog(i2caddress | self.BIT0)\n                    if readlen:\n                        data = self._do_read(readlen)\n                    do_epilog = relax\n                    return data\n                except I2cNackError:\n                    retries -= 1\n                    if not retries:\n                        raise\n                    self.log.warning('Retry exchange')\n                finally:\n                    if do_epilog:\n                        self._do_epilog()\n\n    def poll(self, address: int, write: bool = False,\n             relax: bool = True) -> bool:\n        \"\"\"Poll a remote slave, expect ACK or NACK.\n\n           :param address: the address on the I2C bus, or None to discard start\n           :param write: poll in write mode (vs. read)\n           :param relax: whether to relax the bus (emit STOP) or not\n           :return: True if the slave acknowledged, False otherwise\n        \"\"\"\n        if not self.configured:\n            raise I2cIOError(\"FTDI controller not initialized\")\n        self.validate_address(address)\n        if address is None:\n            i2caddress = None\n        else:\n            i2caddress = (address << 1) & self.HIGH\n            if not write:\n                i2caddress |= self.BIT0\n        do_epilog = True\n        with self._lock:\n            try:\n                self._do_prolog(i2caddress)\n                do_epilog = relax\n                return True\n            except I2cNackError:\n                self.log.info('Not ready')\n                return False\n            finally:\n                if do_epilog:\n                    self._do_epilog()\n\n    def poll_cond(self, address: int, fmt: str, mask: int, value: int,\n                  count: int, relax: bool = True) -> Optional[bytes]:\n        \"\"\"Poll a remove slave, watching for condition to satisfy.\n           On each poll cycle, a repeated start condition is emitted, without\n           releasing the I2C bus, and an ACK is returned to the slave.\n\n           If relax is set, this method releases the I2C bus however it leaves.\n\n           :param address: the address on the I2C bus, or None to discard start\n           :param fmt: struct format for poll register\n           :param mask: binary mask to apply on the condition register\n                before testing for the value\n           :param value: value to test the masked condition register\n                against. Condition is satisfied when register & mask == value\n           :param count: maximum poll count before raising a timeout\n           :param relax: whether to relax the bus (emit STOP) or not\n           :return: the polled register value, or None if poll failed\n        \"\"\"\n        if not self.configured:\n            raise I2cIOError(\"FTDI controller not initialized\")\n        self.validate_address(address)\n        if address is None:\n            i2caddress = None\n        else:\n            i2caddress = (address << 1) & self.HIGH\n            i2caddress |= self.BIT0\n        do_epilog = True\n        with self._lock:\n            try:\n                retry = 0\n                while retry < count:\n                    retry += 1\n                    size = scalc(fmt)\n                    self._do_prolog(i2caddress)\n                    data = self._do_read(size)\n                    self.log.debug(\"Poll data: %s\", hexlify(data).decode())\n                    cond, = sunpack(fmt, data)\n                    if (cond & mask) == value:\n                        self.log.debug('Poll condition matched')\n                        break\n                    data = None\n                    self.log.debug('Poll condition not fulfilled: %x/%x',\n                                   cond & mask, value)\n                do_epilog = relax\n                if not data:\n                    self.log.warning('Poll condition failed')\n                return data\n            except I2cNackError:\n                self.log.info('Not ready')\n                return None\n            finally:\n                if do_epilog:\n                    self._do_epilog()\n\n    def flush(self) -> None:\n        \"\"\"Flush the HW FIFOs.\n        \"\"\"\n        if not self.configured:\n            raise I2cIOError(\"FTDI controller not initialized\")\n        with self._lock:\n            self._ftdi.write_data(bytearray(self._immediate))\n            self._ftdi.purge_buffers()\n\n    def read_gpio(self, with_output: bool = False) -> int:\n        \"\"\"Read GPIO port.\n\n           :param with_output: set to unmask output pins\n           :return: the GPIO port pins as a bitfield\n        \"\"\"\n        with self._lock:\n            data = self._read_raw(self._wide_port)\n        value = data & self._gpio_mask\n        if not with_output:\n            value &= ~self._gpio_dir\n        return value\n\n    def write_gpio(self, value: int) -> None:\n        \"\"\"Write GPIO port.\n\n           :param value: the GPIO port pins as a bitfield\n        \"\"\"\n        with self._lock:\n            if (value & self._gpio_dir) != value:\n                raise I2cIOError(f'No such GPO pins: '\n                                 f'{self._gpio_dir:04x}/{value:04x}')\n            # perform read-modify-write\n            use_high = self._wide_port and (self.direction & 0xff00)\n            data = self._read_raw(use_high)\n            data &= ~self._gpio_mask\n            data |= value\n            self._write_raw(data, use_high)\n            self._gpio_low = data & 0xFF & ~self._i2c_mask\n\n    def set_gpio_direction(self, pins: int, direction: int) -> None:\n        \"\"\"Change the direction of the GPIO pins.\n\n           :param pins: which GPIO pins should be reconfigured\n           :param direction: direction bitfield (on for output)\n        \"\"\"\n        with self._lock:\n            self._set_gpio_direction(16 if self._wide_port else 8,\n                                     pins, direction)\n\n    def _set_gpio_direction(self, width: int, pins: int,\n                            direction: int) -> None:\n        if pins & self._i2c_mask:\n            raise I2cIOError('Cannot access I2C pins as GPIO')\n        gpio_mask = (1 << width) - 1\n        gpio_mask &= ~self._i2c_mask\n        if (pins & gpio_mask) != pins:\n            raise I2cIOError('No such GPIO pin(s)')\n        self._gpio_dir &= ~pins\n        self._gpio_dir |= (pins & direction)\n        self._gpio_mask = gpio_mask & pins\n\n    @property\n    def _data_lo(self) -> Tuple[int]:\n        return (Ftdi.SET_BITS_LOW,\n                self.SCL_BIT | self._gpio_low,\n                self.I2C_DIR | (self._gpio_dir & 0xFF))\n\n    @property\n    def _clk_lo_data_hi(self) -> Tuple[int]:\n        return (Ftdi.SET_BITS_LOW,\n                self.SDA_O_BIT | self._gpio_low,\n                self.I2C_DIR | (self._gpio_dir & 0xFF))\n\n    @property\n    def _clk_lo_data_input(self) -> Tuple[int]:\n        return (Ftdi.SET_BITS_LOW,\n                self.LOW | self._gpio_low,\n                self.SCL_BIT | (self._gpio_dir & 0xFF))\n\n    @property\n    def _clk_lo_data_lo(self) -> Tuple[int]:\n        return (Ftdi.SET_BITS_LOW,\n                self._gpio_low,\n                self.I2C_DIR | (self._gpio_dir & 0xFF))\n\n    @property\n    def _clk_input_data_input(self) -> Tuple[int]:\n        return (Ftdi.SET_BITS_LOW,\n                self.I2C_DIR | self._gpio_low,\n                (self._gpio_dir & 0xFF))\n\n    @property\n    def _idle(self) -> Tuple[int]:\n        return (Ftdi.SET_BITS_LOW,\n                self.I2C_DIR | self._gpio_low,\n                self.I2C_DIR | (self._gpio_dir & 0xFF))\n\n    @property\n    def _start(self) -> Tuple[int]:\n        return self._data_lo * self._ck_hd_sta + \\\n               self._clk_lo_data_lo * self._ck_hd_sta\n\n    @property\n    def _stop(self) -> Tuple[int]:\n        return self._clk_lo_data_hi * self._ck_hd_sta + \\\n               self._clk_lo_data_lo * self._ck_hd_sta + \\\n               self._data_lo * self._ck_su_sto + \\\n               self._idle * self._ck_idle\n\n    def _compute_delay_cycles(self, value: Union[int, float]) -> int:\n        # approx ceiling without relying on math module\n        # the bit delay is far from being precisely known anyway\n        bit_delay = self._ftdi.mpsse_bit_delay\n        return max(1, int((value + bit_delay) / bit_delay))\n\n    def _read_raw(self, read_high: bool) -> int:\n        if read_high:\n            cmd = bytes([Ftdi.GET_BITS_LOW,\n                         Ftdi.GET_BITS_HIGH,\n                         Ftdi.SEND_IMMEDIATE])\n            fmt = '<H'\n        else:\n            cmd = bytes([Ftdi.GET_BITS_LOW,\n                         Ftdi.SEND_IMMEDIATE])\n            fmt = 'B'\n        self._ftdi.write_data(cmd)\n        size = scalc(fmt)\n        data = self._ftdi.read_data_bytes(size, 4)\n        if len(data) != size:\n            raise I2cIOError('Cannot read GPIO')\n        value, = sunpack(fmt, data)\n        return value\n\n    def _write_raw(self, data: int, write_high: bool):\n        direction = self.direction\n        low_data = data & 0xFF\n        low_dir = direction & 0xFF\n        if write_high:\n            high_data = (data >> 8) & 0xFF\n            high_dir = (direction >> 8) & 0xFF\n            cmd = bytes([Ftdi.SET_BITS_LOW, low_data, low_dir,\n                         Ftdi.SET_BITS_HIGH, high_data, high_dir])\n        else:\n            cmd = bytes([Ftdi.SET_BITS_LOW, low_data, low_dir])\n        self._ftdi.write_data(cmd)\n\n    def _do_prolog(self, i2caddress: Optional[int]) -> None:\n        if i2caddress is None:\n            return\n        self.log.debug('   prolog 0x%x', i2caddress >> 1)\n        cmd = bytearray(self._idle * self._ck_delay)\n        cmd.extend(self._start)\n        cmd.extend(self._write_byte)\n        cmd.append(i2caddress)\n        try:\n            self._send_check_ack(cmd)\n        except I2cNackError:\n            self.log.warning('NACK @ 0x%02x', (i2caddress >> 1))\n            raise\n\n    def _do_epilog(self) -> None:\n        self.log.debug('   epilog')\n        cmd = bytearray(self._stop)\n        if self._fake_tristate:\n            # SCL high-Z, SDA high-Z\n            cmd.extend(self._clk_input_data_input)\n        self._ftdi.write_data(cmd)\n        # be sure to purge the MPSSE reply\n        self._ftdi.read_data_bytes(1, 1)\n\n    def _send_check_ack(self, cmd: bytearray):\n        # note: cmd is modified\n        if self._fake_tristate:\n            # SCL low, SDA high-Z (input)\n            cmd.extend(self._clk_lo_data_input)\n            # read SDA (ack from slave)\n            cmd.extend(self._read_bit)\n        else:\n            # SCL low, SDA high-Z\n            cmd.extend(self._clk_lo_data_hi)\n            # read SDA (ack from slave)\n            cmd.extend(self._read_bit)\n        cmd.extend(self._immediate)\n        self._i2c_write_data(cmd)\n        ack = self._i2c_read_data_bytes(1, 4)\n        if not ack:\n            raise I2cIOError('No answer from FTDI')\n        if ack[0] & self.BIT0:\n            raise I2cNackError('NACK from slave')\n\n    def _i2c_write_data(self, cmd: bytearray):\n        if self._clkstrch:\n            cmd.insert(0, self._ftdi.ENABLE_CLK_ADAPTIVE)\n            cmd.append(self._ftdi.DISABLE_CLK_ADAPTIVE)\n        self._ftdi.write_data(cmd)\n\n    def _i2c_read_data_bytes(self, readlen: int, attempt: int = 1,\n                             request_gen=None) -> bytearray:\n        data = self._ftdi.read_data_bytes(readlen, attempt, request_gen)\n        if not data and self._clkstrch:\n            self.log.warning('bus seems wedged')\n            self._ftdi.purge_rx_buffer()\n            self._ftdi.write_data(\n                bytearray((self._ftdi.DISABLE_CLK_ADAPTIVE,)))\n        return data\n\n    def _do_read(self, readlen: int) -> bytearray:\n        self.log.debug('- read %d byte(s)', readlen)\n        if not readlen:\n            # force a real read request on device, but discard any result\n            cmd = bytearray()\n            cmd.extend(self._immediate)\n            self._i2c_write_data(cmd)\n            self._i2c_read_data_bytes(0, 4)\n            return bytearray()\n        if self._fake_tristate:\n            read_byte = (self._clk_lo_data_input +\n                         self._read_byte +\n                         self._clk_lo_data_hi)\n            read_not_last = (read_byte + self._ack +\n                             self._clk_lo_data_lo * self._ck_delay)\n            read_last = (read_byte + self._nack +\n                         self._clk_lo_data_hi * self._ck_delay)\n        else:\n            read_not_last = (self._read_byte + self._ack +\n                             self._clk_lo_data_hi * self._ck_delay)\n            read_last = (self._read_byte + self._nack +\n                         self._clk_lo_data_hi * self._ck_delay)\n        # maximum RX size to fit in FTDI FIFO, minus 2 status bytes\n        chunk_size = self._rx_size-2\n        cmd_size = len(read_last)\n        # limit RX chunk size to the count of I2C packable commands in the FTDI\n        # TX FIFO (minus one byte for the last 'send immediate' command)\n        tx_count = (self._tx_size-1) // cmd_size\n        chunk_size = min(tx_count, chunk_size)\n        chunks = []\n        cmd = None\n        rem = readlen\n        if self._read_optim and rem > chunk_size:\n            chunk_size //= 2\n            self.log.debug('Use optimized transfer, %d byte at a time',\n                           chunk_size)\n            cmd_chunk = bytearray()\n            cmd_chunk.extend(read_not_last * chunk_size)\n            cmd_chunk.extend(self._immediate)\n\n            def write_command_gen(length: int) -> bytearray:\n                if length <= 0:\n                    # no more data\n                    return bytearray()\n                if length <= chunk_size:\n                    cmd = bytearray()\n                    cmd.extend(read_not_last * (length-1))\n                    cmd.extend(read_last)\n                    cmd.extend(self._immediate)\n                    return cmd\n                return cmd_chunk\n\n            while rem:\n                buf = self._i2c_read_data_bytes(rem, 4, write_command_gen)\n                self.log.debug('- read %d bytes, rem: %d', len(buf), rem)\n                chunks.append(buf)\n                rem -= len(buf)\n        else:\n            while rem:\n                size = rem\n                if rem > chunk_size:\n                    if not cmd:\n                        # build the command sequence only once, as it may be\n                        # repeated till the end of the transfer\n                        cmd = bytearray()\n                        cmd.extend(read_not_last * chunk_size)\n                    size = chunk_size\n                else:\n                    cmd = bytearray()\n                    cmd.extend(read_not_last * (rem-1))\n                    cmd.extend(read_last)\n                    cmd.extend(self._immediate)\n                self._i2c_write_data(cmd)\n                buf = self._i2c_read_data_bytes(size, 4)\n                self.log.debug('- read %d byte(s): %s',\n                               len(buf), hexlify(buf).decode())\n                chunks.append(buf)\n                rem -= size\n        return bytearray(b''.join(chunks))\n\n    def _do_write(self, out: Union[bytes, bytearray, Iterable[int]]):\n        if not isinstance(out, bytearray):\n            out = bytearray(out)\n        if not out:\n            return\n        self.log.debug('- write %d byte(s): %s',\n                       len(out), hexlify(out).decode())\n        for byte in out:\n            if self._fake_tristate:\n                # leave SCL low, restore SDA as output\n                cmd = bytearray(self._clk_lo_data_hi)\n                cmd.extend(self._write_byte)\n            else:\n                cmd = bytearray(self._write_byte)\n            cmd.append(byte)\n            self._send_check_ack(cmd)\n"
  },
  {
    "path": "pyftdi/jtag.py",
    "content": "# Copyright (c) 2010-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2016, Emmanuel Bouaziz <ebouaziz@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"JTAG support for PyFdti\"\"\"\n\n# pylint: disable=invalid-name\n# pylint: disable=missing-function-docstring\n\nfrom time import sleep\nfrom typing import List, Tuple, Union\nfrom .ftdi import Ftdi\nfrom .bits import BitSequence\n\n\nclass JtagError(Exception):\n    \"\"\"Generic JTAG error.\"\"\"\n\n\nclass JtagState:\n    \"\"\"Test Access Port controller state\"\"\"\n\n    def __init__(self, name: str, modes: Tuple[str, str]):\n        self.name = name\n        self.modes = modes\n        self.exits = [self, self]  # dummy value before initial configuration\n\n    def __str__(self):\n        return self.name\n\n    def __repr__(self):\n        return self.name\n\n    def setx(self, fstate: 'JtagState', tstate: 'JtagState'):\n        self.exits = [fstate, tstate]\n\n    def getx(self, event):\n        x = int(bool(event))\n        return self.exits[x]\n\n    def is_of(self, mode: str) -> bool:\n        return mode in self.modes\n\n\nclass JtagStateMachine:\n    \"\"\"Test Access Port controller state machine.\"\"\"\n\n    def __init__(self):\n        self.states = {}\n        for s, modes in [('test_logic_reset', ('reset', ' idle')),\n                         ('run_test_idle', ('idle',)),\n                         ('select_dr_scan', ('dr',)),\n                         ('capture_dr', ('dr', 'shift', 'capture')),\n                         ('shift_dr', ('dr', 'shift')),\n                         ('exit_1_dr', ('dr', 'update', 'pause')),\n                         ('pause_dr', ('dr', 'pause')),\n                         ('exit_2_dr', ('dr', 'shift', 'udpate')),\n                         ('update_dr', ('dr', 'idle')),\n                         ('select_ir_scan', ('ir',)),\n                         ('capture_ir', ('ir', 'shift', 'capture')),\n                         ('shift_ir', ('ir', 'shift')),\n                         ('exit_1_ir', ('ir', 'udpate', 'pause')),\n                         ('pause_ir', ('ir', 'pause')),\n                         ('exit_2_ir', ('ir', 'shift', 'update')),\n                         ('update_ir', ('ir', 'idle'))]:\n            self.states[s] = JtagState(s, modes)\n        self['test_logic_reset'].setx(self['run_test_idle'],\n                                      self['test_logic_reset'])\n        self['run_test_idle'].setx(self['run_test_idle'],\n                                   self['select_dr_scan'])\n        self['select_dr_scan'].setx(self['capture_dr'],\n                                    self['select_ir_scan'])\n        self['capture_dr'].setx(self['shift_dr'], self['exit_1_dr'])\n        self['shift_dr'].setx(self['shift_dr'], self['exit_1_dr'])\n        self['exit_1_dr'].setx(self['pause_dr'], self['update_dr'])\n        self['pause_dr'].setx(self['pause_dr'], self['exit_2_dr'])\n        self['exit_2_dr'].setx(self['shift_dr'], self['update_dr'])\n        self['update_dr'].setx(self['run_test_idle'],\n                               self['select_dr_scan'])\n        self['select_ir_scan'].setx(self['capture_ir'],\n                                    self['test_logic_reset'])\n        self['capture_ir'].setx(self['shift_ir'], self['exit_1_ir'])\n        self['shift_ir'].setx(self['shift_ir'], self['exit_1_ir'])\n        self['exit_1_ir'].setx(self['pause_ir'], self['update_ir'])\n        self['pause_ir'].setx(self['pause_ir'], self['exit_2_ir'])\n        self['exit_2_ir'].setx(self['shift_ir'], self['update_ir'])\n        self['update_ir'].setx(self['run_test_idle'], self['select_dr_scan'])\n        self._current = self['test_logic_reset']\n\n    def __getitem__(self, name: str) -> JtagState:\n        return self.states[name]\n\n    def state(self) -> JtagState:\n        return self._current\n\n    def state_of(self, mode: str) -> bool:\n        return self._current.is_of(mode)\n\n    def reset(self):\n        self._current = self['test_logic_reset']\n\n    def find_path(self, target: Union[JtagState, str],\n                  source: Union[JtagState, str, None] = None) \\\n            -> List[JtagState]:\n        \"\"\"Find the shortest event sequence to move from source state to\n           target state. If source state is not specified, used the current\n           state.\n\n           :return: the list of states, including source and target states.\n        \"\"\"\n        if source is None:\n            source = self.state()\n        if isinstance(source, str):\n            source = self[source]\n        if isinstance(target, str):\n            target = self[target]\n\n        def next_path(state, target, path):\n            # this test match the target, path is valid\n            if state == target:\n                return path+[state]\n            # candidate paths\n            paths = []\n            for x in state.exits:\n                # next state is self (loop around), kill the path\n                if x == state:\n                    continue\n                # next state already in upstream (loop back), kill the path\n                if x in path:\n                    continue\n                # try the current path\n                npath = next_path(x, target, path + [state])\n                # downstream is a valid path, store it\n                if npath:\n                    paths.append(npath)\n            # keep the shortest path\n            return min(((len(p), p) for p in paths), key=lambda x: x[0])[1] \\\n                if paths else []\n        return next_path(source, target, [])\n\n    @classmethod\n    def get_events(cls, path):\n        \"\"\"Build up an event sequence from a state sequence, so that the\n           resulting event sequence allows the JTAG state machine to advance\n           from the first state to the last one of the input sequence\"\"\"\n        events = []\n        for s, d in zip(path[:-1], path[1:]):\n            for e, x in enumerate(s.exits):\n                if x == d:\n                    events.append(e)\n        if len(events) != len(path) - 1:\n            raise JtagError(\"Invalid path\")\n        return BitSequence(events)\n\n    def handle_events(self, events):\n        for event in events:\n            self._current = self._current.getx(event)\n\n\nclass JtagController:\n    \"\"\"JTAG master of an FTDI device\"\"\"\n\n    TCK_BIT = 0x01   # FTDI output\n    TDI_BIT = 0x02   # FTDI output\n    TDO_BIT = 0x04   # FTDI input\n    TMS_BIT = 0x08   # FTDI output\n    TRST_BIT = 0x10  # FTDI output, not available on 2232 JTAG debugger\n    JTAG_MASK = 0x1f\n    FTDI_PIPE_LEN = 512\n\n    # Private API\n    def __init__(self, trst: bool = False, frequency: float = 3.0E6):\n        \"\"\"\n        trst uses the nTRST optional JTAG line to hard-reset the TAP\n          controller\n        \"\"\"\n        self._ftdi = Ftdi()\n        self._trst = trst\n        self._frequency = frequency\n        self.direction = (JtagController.TCK_BIT |\n                          JtagController.TDI_BIT |\n                          JtagController.TMS_BIT |\n                          (self._trst and JtagController.TRST_BIT or 0))\n        self._last = None  # Last deferred TDO bit\n        self._write_buff = bytearray()\n\n    # Public API\n    def configure(self, url: str) -> None:\n        \"\"\"Configure the FTDI interface as a JTAG controller\"\"\"\n        self._ftdi.open_mpsse_from_url(\n            url, direction=self.direction, frequency=self._frequency)\n        # FTDI requires to initialize all GPIOs before MPSSE kicks in\n        cmd = bytearray((Ftdi.SET_BITS_LOW, 0x0, self.direction))\n        self._ftdi.write_data(cmd)\n\n    def close(self, freeze: bool = False) -> None:\n        \"\"\"Close the JTAG interface/port.\n\n           :param freeze: if set, FTDI port is not reset to its default\n                          state on close. This means the port is left with\n                          its current configuration and output signals.\n                          This feature should not be used except for very\n                          specific needs.\n        \"\"\"\n        if self._ftdi.is_connected:\n            self._ftdi.close(freeze)\n\n    def purge(self) -> None:\n        self._ftdi.purge_buffers()\n\n    def reset(self, sync: bool = False) -> None:\n        \"\"\"Reset the attached TAP controller.\n           sync sends the command immediately (no caching)\n        \"\"\"\n        # we can either send a TRST HW signal or perform 5 cycles with TMS=1\n        # to move the remote TAP controller back to 'test_logic_reset' state\n        # do both for now\n        if not self._ftdi.is_connected:\n            raise JtagError(\"FTDI controller terminated\")\n        if self._trst:\n            # nTRST\n            value = 0\n            cmd = bytearray((Ftdi.SET_BITS_LOW, value, self.direction))\n            self._ftdi.write_data(cmd)\n            sleep(0.1)\n            # nTRST should be left to the high state\n            value = JtagController.TRST_BIT\n            cmd = bytearray((Ftdi.SET_BITS_LOW, value, self.direction))\n            self._ftdi.write_data(cmd)\n            sleep(0.1)\n        # TAP reset (even with HW reset, could be removed though)\n        self.write_tms(BitSequence('11111'))\n        if sync:\n            self.sync()\n\n    def sync(self) -> None:\n        if not self._ftdi.is_connected:\n            raise JtagError(\"FTDI controller terminated\")\n        if self._write_buff:\n            self._ftdi.write_data(self._write_buff)\n            self._write_buff = bytearray()\n\n    def write_tms(self, tms: BitSequence,\n                  should_read: bool = False) -> None:\n        \"\"\"Change the TAP controller state\"\"\"\n        if not isinstance(tms, BitSequence):\n            raise JtagError('Expect a BitSequence')\n        length = len(tms)\n        if not 0 < length < 8:\n            raise JtagError('Invalid TMS length')\n        out = BitSequence(tms, length=8)\n        # apply the last TDO bit\n        if self._last is not None:\n            out[7] = self._last\n        # print(\"TMS\", tms, (self._last is not None) and 'w/ Last' or '')\n        # reset last bit\n        self._last = None\n        if should_read:\n            cmd = bytearray((Ftdi.RW_BITS_TMS_PVE_NVE, length-1, out.tobyte()))\n        else:\n            cmd = bytearray((Ftdi.WRITE_BITS_TMS_NVE, length-1, out.tobyte()))\n        self._stack_cmd(cmd)\n        if should_read:\n            self.sync()\n\n    def read(self, length: int) -> BitSequence:\n        \"\"\"Read out a sequence of bits from TDO.\"\"\"\n        byte_count = length//8\n        bit_count = length-8*byte_count\n        bs = BitSequence()\n        if byte_count:\n            bytes_ = self._read_bytes(byte_count)\n            bs.append(bytes_)\n        if bit_count:\n            bits = self._read_bits(bit_count)\n            bs.append(bits)\n        return bs\n\n    def write(self, out: Union[BitSequence, str], use_last: bool = True):\n        \"\"\"Write a sequence of bits to TDI\"\"\"\n        if isinstance(out, str):\n            if len(out) > 1:\n                self._write_bytes_raw(out[:-1])\n                out = out[-1]\n            out = BitSequence(bytes_=out)\n        elif not isinstance(out, BitSequence):\n            out = BitSequence(out)\n        if use_last:\n            (out, self._last) = (out[:-1], bool(out[-1]))\n        byte_count = len(out)//8\n        pos = 8*byte_count\n        bit_count = len(out)-pos\n        if byte_count:\n            self._write_bytes(out[:pos])\n        if bit_count:\n            self._write_bits(out[pos:])\n\n    def write_with_read(self, out: BitSequence,\n                        use_last: bool = False) -> int:\n        \"\"\"Write the given BitSequence while reading the same\n           number of bits into the FTDI read buffer.\n           Returns the number of bits written.\"\"\"\n        if not isinstance(out, BitSequence):\n            return JtagError('Expect a BitSequence')\n        if use_last:\n            (out, self._last) = (out[:-1], int(out[-1]))\n        byte_count = len(out)//8\n        pos = 8*byte_count\n        bit_count = len(out)-pos\n        if not byte_count and not bit_count:\n            raise JtagError(\"Nothing to shift\")\n        if byte_count:\n            blen = byte_count-1\n            # print(\"RW OUT %s\" % out[:pos])\n            cmd = bytearray((Ftdi.RW_BYTES_PVE_NVE_LSB,\n                             blen, (blen >> 8) & 0xff))\n            cmd.extend(out[:pos].tobytes(msby=True))\n            self._stack_cmd(cmd)\n            # print(\"push %d bytes\" % byte_count)\n        if bit_count:\n            # print(\"RW OUT %s\" % out[pos:])\n            cmd = bytearray((Ftdi.RW_BITS_PVE_NVE_LSB, bit_count-1))\n            cmd.append(out[pos:].tobyte())\n            self._stack_cmd(cmd)\n            # print(\"push %d bits\" % bit_count)\n        return len(out)\n\n    def read_from_buffer(self, length) -> BitSequence:\n        \"\"\"Read the specified number of bits from the FTDI read buffer.\"\"\"\n        self.sync()\n        bs = BitSequence()\n        byte_count = length//8\n        pos = 8*byte_count\n        bit_count = length-pos\n        if byte_count:\n            data = self._ftdi.read_data_bytes(byte_count, 4)\n            if not data:\n                raise JtagError('Unable to read data from FTDI')\n            byteseq = BitSequence(bytes_=data, length=8*byte_count)\n            # print(\"RW IN %s\" % byteseq)\n            bs.append(byteseq)\n            # print(\"pop %d bytes\" % byte_count)\n        if bit_count:\n            data = self._ftdi.read_data_bytes(1, 4)\n            if not data:\n                raise JtagError('Unable to read data from FTDI')\n            byte = data[0]\n            # need to shift bits as they are shifted in from the MSB in FTDI\n            byte >>= 8-bit_count\n            bitseq = BitSequence(byte, length=bit_count)\n            bs.append(bitseq)\n            # print(\"pop %d bits\" % bit_count)\n        if len(bs) != length:\n            raise ValueError(\"Internal error\")\n        return bs\n\n    @property\n    def ftdi(self) -> Ftdi:\n        \"\"\"Return the Ftdi instance.\n\n           :return: the Ftdi instance\n        \"\"\"\n        return self._ftdi\n\n    def _stack_cmd(self, cmd: Union[bytes, bytearray]):\n        if not isinstance(cmd, (bytes, bytearray)):\n            raise TypeError('Expect bytes or bytearray')\n        if not self._ftdi:\n            raise JtagError(\"FTDI controller terminated\")\n        # Currrent buffer + new command + send_immediate\n        if (len(self._write_buff)+len(cmd)+1) >= JtagController.FTDI_PIPE_LEN:\n            self.sync()\n        self._write_buff.extend(cmd)\n\n    def _read_bits(self, length: int):\n        \"\"\"Read out bits from TDO\"\"\"\n        if length > 8:\n            raise JtagError(\"Cannot fit into FTDI fifo\")\n        cmd = bytearray((Ftdi.READ_BITS_NVE_LSB, length-1))\n        self._stack_cmd(cmd)\n        self.sync()\n        data = self._ftdi.read_data_bytes(1, 4)\n        # need to shift bits as they are shifted in from the MSB in FTDI\n        byte = data[0] >> 8-length\n        bs = BitSequence(byte, length=length)\n        # print(\"READ BITS %s\" % bs)\n        return bs\n\n    def _write_bits(self, out: BitSequence) -> None:\n        \"\"\"Output bits on TDI\"\"\"\n        length = len(out)\n        byte = out.tobyte()\n        # print(\"WRITE BITS %s\" % out)\n        cmd = bytearray((Ftdi.WRITE_BITS_NVE_LSB, length-1, byte))\n        self._stack_cmd(cmd)\n\n    def _read_bytes(self, length: int) -> BitSequence:\n        \"\"\"Read out bytes from TDO\"\"\"\n        if length > JtagController.FTDI_PIPE_LEN:\n            raise JtagError(\"Cannot fit into FTDI fifo\")\n        alen = length-1\n        cmd = bytearray((Ftdi.READ_BYTES_NVE_LSB, alen & 0xff,\n                         (alen >> 8) & 0xff))\n        self._stack_cmd(cmd)\n        self.sync()\n        data = self._ftdi.read_data_bytes(length, 4)\n        bs = BitSequence(bytes_=data, length=8*length)\n        # print(\"READ BYTES %s\" % bs)\n        return bs\n\n    def _write_bytes(self, out: BitSequence):\n        \"\"\"Output bytes on TDI\"\"\"\n        bytes_ = out.tobytes(msby=True)  # don't ask...\n        olen = len(bytes_)-1\n        # print(\"WRITE BYTES %s\" % out)\n        cmd = bytearray((Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xff,\n                         (olen >> 8) & 0xff))\n        cmd.extend(bytes_)\n        self._stack_cmd(cmd)\n\n    def _write_bytes_raw(self, out: BitSequence):\n        \"\"\"Output bytes on TDI\"\"\"\n        olen = len(out)-1\n        cmd = bytearray((Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xff,\n                         (olen >> 8) & 0xff))\n        cmd.extend(out)\n        self._stack_cmd(cmd)\n\n\nclass JtagEngine:\n    \"\"\"High-level JTAG engine controller\"\"\"\n\n    def __init__(self, trst: bool = False, frequency: float = 3E06):\n        self._ctrl = JtagController(trst, frequency)\n        self._sm = JtagStateMachine()\n        self._seq = bytearray()\n\n    @property\n    def state_machine(self):\n        return self._sm\n\n    @property\n    def controller(self):\n        return self._ctrl\n\n    def configure(self, url: str) -> None:\n        \"\"\"Configure the FTDI interface as a JTAG controller\"\"\"\n        self._ctrl.configure(url)\n\n    def close(self, freeze: bool = False) -> None:\n        \"\"\"Terminate a JTAG session/connection.\n\n           :param freeze: if set, FTDI port is not reset to its default\n                          state on close.\n        \"\"\"\n        self._ctrl.close(freeze)\n\n    def purge(self) -> None:\n        \"\"\"Purge low level HW buffers\"\"\"\n        self._ctrl.purge()\n\n    def reset(self) -> None:\n        \"\"\"Reset the attached TAP controller\"\"\"\n        self._ctrl.reset()\n        self._sm.reset()\n\n    def write_tms(self, out, should_read=False) -> None:\n        \"\"\"Change the TAP controller state\"\"\"\n        self._ctrl.write_tms(out, should_read=should_read)\n\n    def read(self, length):\n        \"\"\"Read out a sequence of bits from TDO\"\"\"\n        return self._ctrl.read(length)\n\n    def write(self, out, use_last=False) -> None:\n        \"\"\"Write a sequence of bits to TDI\"\"\"\n        self._ctrl.write(out, use_last)\n\n    def get_available_statenames(self):\n        \"\"\"Return a list of supported state name\"\"\"\n        return [str(s) for s in self._sm.states]\n\n    def change_state(self, statename) -> None:\n        \"\"\"Advance the TAP controller to the defined state\"\"\"\n        # find the state machine path to move to the new instruction\n        path = self._sm.find_path(statename)\n        # convert the path into an event sequence\n        events = self._sm.get_events(path)\n        # update the remote device tap controller\n        self._ctrl.write_tms(events)\n        # update the current state machine's state\n        self._sm.handle_events(events)\n\n    def go_idle(self) -> None:\n        \"\"\"Change the current TAP controller to the IDLE state\"\"\"\n        self.change_state('run_test_idle')\n\n    def write_ir(self, instruction) -> None:\n        \"\"\"Change the current instruction of the TAP controller\"\"\"\n        self.change_state('shift_ir')\n        self._ctrl.write(instruction)\n        self.change_state('update_ir')\n\n    def capture_ir(self) -> None:\n        \"\"\"Capture the current instruction from the TAP controller\"\"\"\n        self.change_state('capture_ir')\n\n    def write_dr(self, data) -> None:\n        \"\"\"Change the data register of the TAP controller\"\"\"\n        self.change_state('shift_dr')\n        self._ctrl.write(data)\n        self.change_state('update_dr')\n\n    def read_dr(self, length: int) -> BitSequence:\n        \"\"\"Read the data register from the TAP controller\"\"\"\n        self.change_state('shift_dr')\n        data = self._ctrl.read(length)\n        self.change_state('update_dr')\n        return data\n\n    def capture_dr(self) -> None:\n        \"\"\"Capture the current data register from the TAP controller\"\"\"\n        self.change_state('capture_dr')\n\n    def sync(self) -> None:\n        self._ctrl.sync()\n\n    def shift_register(self, out: BitSequence) -> BitSequence:\n        if not self._sm.state_of('shift'):\n            raise JtagError(f'Invalid state: {self._sm.state()}')\n        if self._sm.state_of('capture'):\n            bs = BitSequence(False)\n            self._ctrl.write_tms(bs)\n            self._sm.handle_events(bs)\n\n        bits_out = self._ctrl.write_with_read(out)\n        bs = self._ctrl.read_from_buffer(bits_out)\n        if len(bs) != len(out):\n            raise ValueError(\"Internal error\")\n\n        return bs\n\n    def shift_and_update_register(self, out: BitSequence) -> BitSequence:\n        \"\"\"Shift a BitSequence into the current register and retrieve the\n           register output, advancing the state to update_*r\"\"\"\n        if not self._sm.state_of('shift'):\n            raise JtagError(f'Invalid state: {self._sm.state()}')\n        if self._sm.state_of('capture'):\n            bs = BitSequence(False)\n            self._ctrl.write_tms(bs)\n            self._sm.handle_events(bs)\n\n        # Write with read using the last bit for next TMS transition\n        bits_out = self._ctrl.write_with_read(out, use_last=True)\n\n        # Advance the state from shift to update\n        events = BitSequence('11')\n        self.write_tms(events, should_read=True)\n        # (write_tms calls sync())\n        # update the current state machine's state\n        self._sm.handle_events(events)\n\n        # Read the bits clocked out as part of the initial write\n        bs = self._ctrl.read_from_buffer(bits_out)\n\n        # Read the last two bits clocked out with TMS above\n        # (but only use the lowest bit in the return data)\n        last_bits = self._ctrl.read_from_buffer(2)\n        bs.append(BitSequence((last_bits.tobyte() & 0x1), length=1))\n        if len(bs) != len(out):\n            raise ValueError(\"Internal error\")\n\n        return bs\n\n\nclass JtagTool:\n    \"\"\"A helper class with facility functions\"\"\"\n\n    def __init__(self, engine):\n        self._engine = engine\n\n    def idcode(self) -> None:\n        idcode = self._engine.read_dr(32)\n        self._engine.go_idle()\n        return int(idcode)\n\n    def preload(self, bsdl, data) -> None:\n        instruction = bsdl.get_jtag_ir('preload')\n        self._engine.write_ir(instruction)\n        self._engine.write_dr(data)\n        self._engine.go_idle()\n\n    def sample(self, bsdl):\n        instruction = bsdl.get_jtag_ir('sample')\n        self._engine.write_ir(instruction)\n        data = self._engine.read_dr(bsdl.get_boundary_length())\n        self._engine.go_idle()\n        return data\n\n    def extest(self, bsdl) -> None:\n        instruction = bsdl.get_jtag_ir('extest')\n        self._engine.write_ir(instruction)\n\n    def readback(self, bsdl):\n        data = self._engine.read_dr(bsdl.get_boundary_length())\n        self._engine.go_idle()\n        return data\n\n    def detect_register_size(self) -> int:\n        # Freely inpired from UrJTAG\n        stm = self._engine.state_machine\n        if not stm.state_of('shift'):\n            raise JtagError(f'Invalid state: {stm.state()}')\n        if stm.state_of('capture'):\n            bs = BitSequence(False)\n            self._engine.controller.write_tms(bs)\n            stm.handle_events(bs)\n        MAX_REG_LEN = 1024\n        PATTERN_LEN = 8\n        stuck = None\n        for length in range(1, MAX_REG_LEN):\n            print(f'Testing for length {length}')\n            if length > 5:\n                raise ValueError(f'Abort detection over reg length {length}')\n            zero = BitSequence(length=length)\n            inj = BitSequence(length=length+PATTERN_LEN)\n            inj.inc()\n            ok = False\n            for _ in range(1, 1 << PATTERN_LEN):\n                ok = False\n                self._engine.write(zero, False)\n                rcv = self._engine.shift_register(inj)\n                try:\n                    tdo = rcv.invariant()\n                except ValueError:\n                    tdo = None\n                if stuck is None:\n                    stuck = tdo\n                if stuck != tdo:\n                    stuck = None\n                rcv >>= length\n                if rcv == inj:\n                    ok = True\n                else:\n                    break\n                inj.inc()\n            if ok:\n                print(f'Register detected length: {length}')\n                return length\n        if stuck is not None:\n            raise JtagError('TDO seems to be stuck')\n        raise JtagError('Unable to detect register length')\n"
  },
  {
    "path": "pyftdi/misc.py",
    "content": "# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2008-2016, Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"Miscellaneous helpers\"\"\"\n\n# pylint: disable=invalid-name\n# pylint: disable=import-outside-toplevel\n\nfrom array import array\nfrom copy import deepcopy\nfrom re import match\nfrom typing import Any, Iterable, Optional, Sequence, Union\n\n\n# String values evaluated as true boolean values\nTRUE_BOOLEANS = ['on', 'true', 'enable', 'enabled', 'yes', 'high', '1']\n# String values evaluated as false boolean values\nFALSE_BOOLEANS = ['off', 'false', 'disable', 'disabled', 'no', 'low', '0']\n# ASCII or '.' filter\nASCIIFILTER = ''.join([((len(repr(chr(_x))) == 3) or (_x == 0x5c)) and chr(_x)\n                       or '.' for _x in range(128)]) + '.' * 128\nASCIIFILTER = bytearray(ASCIIFILTER.encode('ascii'))\n\n\ndef hexdump(data: Union[bytes, bytearray, Iterable[int]],\n            full: bool = False, abbreviate: bool = False) -> str:\n    \"\"\"Convert a binary buffer into a hexadecimal representation.\n\n       Return a multi-line strings with hexadecimal values and ASCII\n       representation of the buffer data.\n\n       :param data: binary buffer to dump\n       :param full: use `hexdump -Cv` format\n       :param abbreviate: replace identical lines with '*'\n       :return: the generated string\n    \"\"\"\n    try:\n        if isinstance(data, (bytes, array)):\n            src = bytearray(data)\n        elif not isinstance(data, bytearray):\n            # data may be a list/tuple\n            src = bytearray(b''.join(data))\n        else:\n            src = data\n    except Exception as exc:\n        raise TypeError(f\"Unsupported data type '{type(data)}'\") from exc\n\n    length = 16\n    result = []\n    last = b''\n    abv = False\n    for i in range(0, len(src), length):\n        s = src[i:i+length]\n        if abbreviate:\n            if s == last:\n                if not abv:\n                    result.append('*\\n')\n                    abv = True\n                continue\n            abv = False\n        hexa = ' '.join((f'{x:02x}' for x in s))\n        printable = s.translate(ASCIIFILTER).decode('ascii')\n        if full:\n            hx1, hx2 = hexa[:3*8], hexa[3*8:]\n            hl = length//2\n            result.append(f'{i:08x}  {hx1:<{hl*3}} {hx2:<{hl*3}} '\n                          f'|{printable}|\\n')\n        else:\n            result.append(f'{i:06x}   {hexa:<{length*3}}  {printable}\\n')\n        last = s\n    return ''.join(result)\n\n\ndef hexline(data: Union[bytes, bytearray, Iterable[int]],\n            sep: str = ' ') -> str:\n    \"\"\"Convert a binary buffer into a hexadecimal representation.\n\n       Return a string with hexadecimal values and ASCII representation\n       of the buffer data.\n\n       :param data: binary buffer to dump\n       :param sep: the separator string/char\n       :return: the formatted string\n    \"\"\"\n    try:\n        if isinstance(data, (bytes, array)):\n            src = bytearray(data)\n        elif not isinstance(data, bytearray):\n            # data may be a list/tuple\n            src = bytearray(b''.join(data))\n        else:\n            src = data\n    except Exception as exc:\n        raise TypeError(f\"Unsupported data type '{type(data)}'\") from exc\n\n    hexa = sep.join((f'{x:02x}' for x in src))\n    printable = src.translate(ASCIIFILTER).decode('ascii')\n    return f'({len(data)}) {hexa} : {printable}'\n\n\ndef to_int(value: Union[int, str]) -> int:\n    \"\"\"Parse a value and convert it into an integer value if possible.\n\n       Input value may be:\n       - a string with an integer coded as a decimal value\n       - a string with an integer coded as a hexadecimal value\n       - a integral value\n       - a integral value with a unit specifier (kilo or mega)\n\n       :param value: input value to convert to an integer\n       :return: the value as an integer\n       :rtype: int\n       :raise ValueError: if the input value cannot be converted into an int\n    \"\"\"\n    if not value:\n        return 0\n    if isinstance(value, int):\n        return value\n    mo = match(r'^\\s*(\\d+)\\s*(?:([KMkm]i?)?B?)?\\s*$', value)\n    if mo:\n        mult = {'K': (1000),\n                'KI': (1 << 10),\n                'M': (1000 * 1000),\n                'MI': (1 << 20)}\n        value = int(mo.group(1))\n        if mo.group(2):\n            value *= mult[mo.group(2).upper()]\n        return value\n    return int(value.strip(), value.startswith('0x') and 16 or 10)\n\n\ndef to_bool(value: Union[int, bool, str], permissive: bool = True,\n            allow_int: bool = False) -> bool:\n    \"\"\"Parse a string and convert it into a boolean value if possible.\n\n       Input value may be:\n       - a string with an integer value, if `allow_int` is enabled\n       - a boolean value\n       - a string with a common boolean definition\n\n       :param value: the value to parse and convert\n       :param permissive: default to the False value if parsing fails\n       :param allow_int: allow an integral type as the input value\n       :raise ValueError: if the input value cannot be converted into an bool\n    \"\"\"\n    if value is None:\n        return False\n    if isinstance(value, bool):\n        return value\n    if isinstance(value, int):\n        if allow_int:\n            return bool(value)\n        if permissive:\n            return False\n        raise ValueError(f\"Invalid boolean value: '{value}'\")\n    if value.lower() in TRUE_BOOLEANS:\n        return True\n    if permissive or (value.lower() in FALSE_BOOLEANS):\n        return False\n    raise ValueError(f\"Invalid boolean value: '{value}'\")\n\n\ndef to_bps(value: str) -> int:\n    \"\"\"Parse a string and convert it into a baudrate value.\n\n       The function accepts common multipliers as K, M and G\n\n       :param value: the value to parse and convert\n       :type value: str or int or float\n       :rtype: float\n       :raise ValueError: if the input value cannot be converted into a float\n    \"\"\"\n    if isinstance(value, float):\n        return int(value)\n    if isinstance(value, int):\n        return value\n    mo = match(r'^(?P<value>[-+]?[0-9]*\\.?[0-9]+(?:[Ee][-+]?[0-9]+)?)'\n               r'(?P<unit>[KkMmGg])?$', value)\n    if not mo:\n        raise ValueError('Invalid frequency')\n    frequency = float(mo.group(1))\n    if mo.group(2):\n        mult = {'K': 1E3, 'M': 1E6, 'G': 1E9}\n        frequency *= mult[mo.group(2).upper()]\n    return int(frequency)\n\n\ndef xor(_a_: bool, _b_: bool) -> bool:\n    \"\"\"XOR logical operation.\n\n       :param _a_: first argument\n       :param _b_: second argument\n       :return: xor-ed value\n    \"\"\"\n    # pylint: disable=superfluous-parens\n    return bool((not (_a_) and _b_) or (_a_ and not (_b_)))\n\n\ndef is_iterable(obj: Any) -> bool:\n    \"\"\"Tells whether an instance is iterable or not.\n\n       :param obj: the instance to test\n       :type obj: object\n       :return: True if the object is iterable\n       :rtype: bool\n    \"\"\"\n    try:\n        iter(obj)\n        return True\n    except TypeError:\n        return False\n\n\ndef pretty_size(size, sep: str = ' ',\n                lim_k: int = 1 << 10, lim_m: int = 10 << 20,\n                plural: bool = True, floor: bool = True) -> str:\n    \"\"\"Convert a size into a more readable unit-indexed size (KiB, MiB)\n\n       :param size: integral value to convert\n       :param sep: the separator character between the integral value and\n            the unit specifier\n       :param lim_k: any value above this limit is a candidate for KiB\n            conversion.\n       :param lim_m: any value above this limit is a candidate for MiB\n            conversion.\n       :param plural: whether to append a final 's' to byte(s)\n       :param floor: how to behave when exact conversion cannot be\n            achieved: take the closest, smaller value or fallback to the next\n            unit that allows the exact representation of the input value\n       :return: the prettyfied size\n    \"\"\"\n    size = int(size)\n    if size > lim_m:\n        ssize = size >> 20\n        if floor or (ssize << 20) == size:\n            return f'{ssize}{sep}MiB'\n    if size > lim_k:\n        ssize = size >> 10\n        if floor or (ssize << 10) == size:\n            return f'{ssize}{sep}KiB'\n    return f'{size}{sep}byte{plural and \"s\" or \"\"}'\n\n\ndef add_custom_devices(ftdicls=None,\n                       vpstr: Optional[Sequence[str]] = None,\n                       force_hex: bool = False) -> None:\n    \"\"\"Helper function to add custom VID/PID to FTDI device identifer map.\n\n       The string to parse should match the following format:\n\n       [vendor_name=]<vendor_id>:[product_name=]<product_id>\n\n       * vendor_name and product_name are optional strings, they may be omitted\n         as they only serve as human-readable aliases for vendor and product\n         names.\n       * vendor_id and product_id are mandatory strings that should resolve\n         as 16-bit integer (USB VID and PID values). They may be expressed as\n         decimal or hexadecimal syntax.\n\n       ex:\n         * ``0x403:0x9999``, vid:pid short syntax, with no alias names\n         * ``mycompany=0x666:myproduct=0xcafe``, vid:pid complete syntax with\n            aliases\n\n       :param vpstr: typically, a option switch string describing the device\n                     to add\n       :param ftdicls: the Ftdi class that should support the new device.\n       :param force_hex: if set, consider that the pid/vid string are\n                         hexadecimal encoded values.\n    \"\"\"\n    from inspect import isclass\n    if not isclass(ftdicls):\n        raise ValueError('Expect Ftdi class, not instance')\n    for vidpid in vpstr or []:\n        vidpids = {vid: set() for vid in ftdicls.PRODUCT_IDS}\n        vname = ''\n        pname = ''\n        try:\n            vid, pid = vidpid.split(':')\n            if '=' in vid:\n                vname, vid = vid.split('=', 1)\n            if '=' in pid:\n                pname, pid = pid.split('=', 1)\n            if force_hex:\n                vid, pid = [int(v, 16) for v in (vid, pid)]\n            else:\n                vid, pid = [to_int(v) for v in (vid, pid)]\n        except ValueError as exc:\n            raise ValueError('Invalid VID:PID value') from exc\n        if vid not in vidpids:\n            ftdicls.add_custom_vendor(vid, vname)\n            vidpids[vid] = set()\n        if pid not in vidpids[vid]:\n            ftdicls.add_custom_product(vid, pid, pname)\n            vidpids[vid].add(pid)\n\n\ndef show_call_stack():\n    \"\"\"Print the current call stack, only useful for debugging purpose.\"\"\"\n    from sys import _current_frames\n    from threading import current_thread\n    from traceback import print_stack\n    print_stack(_current_frames()[current_thread().ident])\n\n\nclass classproperty(property):\n    \"\"\"Getter property decorator for a class\"\"\"\n    # pylint: disable=invalid-name\n    def __get__(self, obj: Any, objtype=None) -> Any:\n        return super().__get__(objtype)\n\n\nclass EasyDict(dict):\n    \"\"\"Dictionary whose members can be accessed as instance members\n    \"\"\"\n\n    def __init__(self, dictionary=None, **kwargs):\n        super().__init__(self)\n        if dictionary is not None:\n            self.update(dictionary)\n        self.update(kwargs)\n\n    def __getattr__(self, name):\n        try:\n            return self.__getitem__(name)\n        except KeyError as exc:\n            raise AttributeError(f\"'{self.__class__.__name__}' object has no \"\n                                 f\"attribute '{name}'\") from exc\n\n    def __setattr__(self, name, value):\n        self.__setitem__(name, value)\n\n    @classmethod\n    def copy(cls, dictionary):\n\n        def _deep_copy(obj):\n            if isinstance(obj, list):\n                return [_deep_copy(v) for v in obj]\n            if isinstance(obj, dict):\n                return EasyDict({k: _deep_copy(obj[k]) for k in obj})\n            return deepcopy(obj)\n        return cls(_deep_copy(dictionary))\n\n    def mirror(self) -> 'EasyDict':\n        \"\"\"Instanciate a mirror EasyDict.\"\"\"\n        return EasyDict({v: k for k, v in self.items()})\n"
  },
  {
    "path": "pyftdi/serialext/__init__.py",
    "content": "# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2008-2015, Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"Serial modules compliant with pyserial APIs\n\"\"\"\n\ntry:\n    from serial.serialutil import SerialException\nexcept ImportError as exc:\n    raise ImportError(\"Python serial module not installed\") from exc\ntry:\n    from serial import VERSION, serial_for_url as serial4url\n    version = tuple(int(x) for x in VERSION.split('.'))\n    if version < (3, 0):\n        raise ValueError\nexcept (ValueError, IndexError, ImportError) as exc:\n    raise ImportError(\"pyserial 3.0+ is required\") from exc\ntry:\n    from serial import protocol_handler_packages\n    protocol_handler_packages.append('pyftdi.serialext')\nexcept ImportError as exc:\n    raise SerialException('Cannot register pyftdi extensions') from exc\n\nserial_for_url = serial4url\n\n\ndef touch():\n    \"\"\"Do nothing, only for static checkers than do not like module import\n       with no module references\n    \"\"\"\n"
  },
  {
    "path": "pyftdi/serialext/logger.py",
    "content": "# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2008-2016, Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=broad-except\n# pylint: disable=invalid-name\n# pylint: disable=missing-function-docstring\n# pylint: disable=missing-module-docstring\n# pylint: disable=no-member\n# pylint: disable=super-with-arguments\n\nfrom sys import stderr\nfrom time import time\nfrom ..misc import hexdump\n\n\n__all__ = ['SerialLogger']\n\n\nclass SerialLogger:\n    \"\"\"Serial port wrapper to log input/output data to a log file.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        logpath = kwargs.pop('logfile', None)\n        if not logpath:\n            raise ValueError('Missing logfile')\n        try:\n            # pylint: disable=consider-using-with\n            self._logger = open(logpath, \"wt\")\n        except IOError as exc:\n            print(f'Cannot log data to {logpath}: {exc}', file=stderr)\n        self._last = time()\n        self._log_init(*args, **kwargs)\n        super(SerialLogger, self).__init__(*args, **kwargs)\n\n    def open(self,):\n        self._log_open()\n        super(SerialLogger, self).open()\n\n    def close(self):\n        self._log_close()\n        self._logger.close()\n        super(SerialLogger, self).close()\n\n    def read(self, size=1):\n        data = super(SerialLogger, self).read(size)\n        self._log_read(data)\n        return data\n\n    def write(self, data):\n        if data:\n            self._log_write(data)\n        super(SerialLogger, self).write(data)\n\n    def flush(self):\n        self._log_flush()\n        super(SerialLogger, self).flush()\n\n    def reset_input_buffer(self):\n        self._log_reset('I')\n        super(SerialLogger, self).reset_input_buffer()\n\n    def reset_output_buffer(self):\n        self._log_reset('O')\n        super(SerialLogger, self).reset_output_buffer()\n\n    def send_break(self, duration=0.25):\n        self._log_signal('BREAK', f'for {duration:.3f}')\n        super(SerialLogger, self).send_break()\n\n    def _update_break_state(self):\n        self._log_signal('BREAK', self._break_state)\n        super(SerialLogger, self)._update_break_state()\n\n    def _update_rts_state(self):\n        self._log_signal('RTS', self._rts_state)\n        super(SerialLogger, self)._update_rts_state()\n\n    def _update_dtr_state(self):\n        self._log_signal('DTR', self._dtr_state)\n        super(SerialLogger, self)._update_dtr_state()\n\n    @property\n    def cts(self):\n        level = super(SerialLogger, self).cts\n        self._log_signal('CTS', level)\n        return level\n\n    @property\n    def dsr(self):\n        level = super(SerialLogger, self).dsr\n        self._log_signal('DSR', level)\n        return level\n\n    @property\n    def ri(self):\n        level = super(SerialLogger, self).ri\n        self._log_signal('RI', level)\n        return level\n\n    @property\n    def cd(self):\n        level = super(SerialLogger, self).cd\n        self._log_signal('CD', level)\n        return level\n\n    def in_waiting(self):\n        count = super(SerialLogger, self).in_waiting()\n        self._log_waiting(count)\n        return count\n\n    def _print(self, header, string=None):\n        if self._logger:\n            now = time()\n            delta = (now-self._last)*1000\n            self._last = now\n            print(f'{header} ({delta:3.3f} ms):\\n{string or \"\"}',\n                  file=self._logger)\n            self._logger.flush()\n\n    def _log_init(self, *args, **kwargs):\n        try:\n            sargs = ', '.join(args)\n            skwargs = ', '.join({f'{it[0]}={it[1]}' for it in kwargs.items()})\n            self._print('NEW', f'  args: {sargs} {skwargs}')\n        except Exception as exc:\n            print(f'Cannot log init ({exc})', file=stderr)\n\n    def _log_open(self):\n        try:\n            self._print('OPEN')\n        except Exception as exc:\n            print(f'Cannot log open ({exc})', file=stderr)\n\n    def _log_close(self):\n        try:\n            self._print('CLOSE')\n        except Exception as exc:\n            print(f'Cannot log close ({exc})', file=stderr)\n\n    def _log_read(self, data):\n        try:\n            self._print('READ', hexdump(data))\n        except Exception as exc:\n            print(f'Cannot log input data ({exc})', file=stderr)\n\n    def _log_write(self, data):\n        try:\n            self._print('WRITE', hexdump(data))\n        except Exception as exc:\n            print(f'Cannot log output data ({exc})', data, file=stderr)\n\n    def _log_flush(self):\n        try:\n            self._print('FLUSH')\n        except Exception as exc:\n            print(f'Cannot log flush action ({exc})', file=stderr)\n\n    def _log_reset(self, type_):\n        try:\n            self._print('RESET BUFFER', type_)\n        except Exception as exc:\n            print(f'Cannot log reset buffer ({exc})', file=stderr)\n\n    def _log_waiting(self, count):\n        try:\n            self._print('INWAITING', f'{count}')\n        except Exception as exc:\n            print(f'Cannot log inwaiting ({exc})', file=stderr)\n\n    def _log_signal(self, name, value):\n        try:\n            self._print(name.upper(), str(value))\n        except Exception as exc:\n            print(f'Cannot log {name} ({exc})', file=stderr)\n"
  },
  {
    "path": "pyftdi/serialext/protocol_ftdi.py",
    "content": "# Copyright (c) 2008-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2008-2016, Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# this file has not been updated for a while, so coding style needs some love\n# pylint: disable=attribute-defined-outside-init\n# pylint: disable=invalid-name\n# pylint: disable=missing-class-docstring\n# pylint: disable=missing-module-docstring\n\nfrom io import RawIOBase\nfrom time import sleep, time as now\nfrom serial import SerialBase, SerialException, VERSION as pyserialver\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.usbtools import UsbToolsError\n\n\nclass FtdiSerial(SerialBase):\n    \"\"\"Base class for Serial port implementation compatible with pyserial API\n       using a USB device.\n    \"\"\"\n\n    BAUDRATES = sorted([9600 * (x+1) for x in range(6)] +\n                       list(range(115200, 1000000, 115200)) +\n                       list(range(1000000, 13000000, 100000)))\n\n    PYSERIAL_VERSION = tuple(int(x) for x in pyserialver.split('.'))\n\n    def open(self):\n        \"\"\"Open the initialized serial port\"\"\"\n        if self.port is None:\n            raise SerialException(\"Port must be configured before use.\")\n        try:\n            device = Ftdi.create_from_url(self.port)\n        except (UsbToolsError, IOError) as exc:\n            raise SerialException(f'Unable to open USB port {self.portstr}: '\n                                  f'{exc}') from exc\n        self.udev = device\n        self._set_open_state(True)\n        self._reconfigure_port()\n\n    def close(self):\n        \"\"\"Close the open port\"\"\"\n        self._set_open_state(False)\n        if self.udev:\n            self.udev.close()\n            self.udev = None\n\n    def read(self, size=1):\n        \"\"\"Read size bytes from the serial port. If a timeout is set it may\n           return less characters as requested. With no timeout it will block\n           until the requested number of bytes is read.\"\"\"\n        data = bytearray()\n        start = now()\n        while True:\n            buf = self.udev.read_data(size)\n            data.extend(buf)\n            size -= len(buf)\n            if size <= 0:\n                break\n            if self._timeout is not None:\n                if buf:\n                    break\n                ms = now()-start\n                if ms > self._timeout:\n                    break\n            sleep(0.01)\n        return bytes(data)\n\n    def write(self, data):\n        \"\"\"Output the given string over the serial port.\"\"\"\n        return self.udev.write_data(data)\n\n    def flush(self):\n        \"\"\"Flush of file like objects. In this case, wait until all data\n           is written.\"\"\"\n\n    def reset_input_buffer(self):\n        \"\"\"Clear input buffer, discarding all that is in the buffer.\"\"\"\n        self.udev.purge_rx_buffer()\n\n    def reset_output_buffer(self):\n        \"\"\"Clear output buffer, aborting the current output and\n        discarding all that is in the buffer.\"\"\"\n        self.udev.purge_tx_buffer()\n\n    def send_break(self, duration=0.25):\n        \"\"\"Send break condition.\"\"\"\n        self.udev.set_break(True)\n        sleep(duration)\n        self.udev.set_break(False)\n\n    def _update_break_state(self):\n        \"\"\"Send break condition. Not supported\"\"\"\n        self.udev.set_break(self._break_state)\n\n    def _update_rts_state(self):\n        \"\"\"Set terminal status line: Request To Send\"\"\"\n        self.udev.set_rts(self._rts_state)\n\n    def _update_dtr_state(self):\n        \"\"\"Set terminal status line: Data Terminal Ready\"\"\"\n        self.udev.set_dtr(self._dtr_state)\n\n    @property\n    def ftdi(self) -> Ftdi:\n        \"\"\"Return the Ftdi instance.\n\n           :return: the Ftdi instance\n        \"\"\"\n        return self.udev\n\n    @property\n    def usb_path(self):\n        \"\"\"Return the physical location as a triplet.\n             * bus is the USB bus\n             * address is the address on the USB bus\n             * interface is the interface number on the FTDI debice\n\n           :return: (bus, address, interface)\n           :rtype: tuple(int)\n        \"\"\"\n        return self.udev.usb_path\n\n    @property\n    def cts(self):\n        \"\"\"Read terminal status line: Clear To Send\"\"\"\n        return self.udev.get_cts()\n\n    @property\n    def dsr(self):\n        \"\"\"Read terminal status line: Data Set Ready\"\"\"\n        return self.udev.get_dsr()\n\n    @property\n    def ri(self):\n        \"\"\"Read terminal status line: Ring Indicator\"\"\"\n        return self.udev.get_ri()\n\n    @property\n    def cd(self):\n        \"\"\"Read terminal status line: Carrier Detect\"\"\"\n        return self.udev.get_cd()\n\n    @property\n    def in_waiting(self):\n        \"\"\"Return the number of characters currently in the input buffer.\"\"\"\n        # not implemented\n        return 0\n\n    @property\n    def out_waiting(self):\n        \"\"\"Return the number of bytes currently in the output buffer.\"\"\"\n        return 0\n\n    @property\n    def fifoSizes(self):\n        \"\"\"Return the (TX, RX) tupple of hardware FIFO sizes\"\"\"\n        return self.udev.fifo_sizes\n\n    def _reconfigure_port(self):\n        try:\n            self._baudrate = self.udev.set_baudrate(self._baudrate, True)\n            self.udev.set_line_property(self._bytesize,\n                                        self._stopbits,\n                                        self._parity)\n            if self._rtscts:\n                self.udev.set_flowctrl('hw')\n            elif self._xonxoff:\n                self.udev.set_flowctrl('sw')\n            else:\n                self.udev.set_flowctrl('')\n            try:\n                self.udev.set_dynamic_latency(12, 200, 50)\n            except AttributeError:\n                # backend does not support this feature\n                pass\n        except IOError as exc:\n            err = self.udev.get_error_string()\n            raise SerialException(f'{exc} ({err})') from exc\n\n    def _set_open_state(self, open_):\n        self.is_open = bool(open_)\n\n\n# assemble Serial class with the platform specific implementation and the base\n# for file-like behavior.\nclass Serial(FtdiSerial, RawIOBase):\n\n    BACKEND = 'pyftdi'\n\n    def __init__(self, *args, **kwargs):\n        RawIOBase.__init__(self)\n        FtdiSerial.__init__(self, *args, **kwargs)\n"
  },
  {
    "path": "pyftdi/serialext/protocol_unix.py",
    "content": "# Copyright (c) 2008-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2016, Emmanuel Bouaziz <ebouaziz@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# this file has not been updated for a while, so coding style needs some love\n# pylint: disable=broad-except\n# pylint: disable=attribute-defined-outside-init\n# pylint: disable=redefined-outer-name\n# pylint: disable=invalid-name\n# pylint: disable=missing-function-docstring\n# pylint: disable=missing-class-docstring\n# pylint: disable=missing-module-docstring\n\nimport errno\nimport os\nimport select\nimport socket\nfrom io import RawIOBase\nfrom serial import (SerialBase, SerialException, PortNotOpenError,\n                    VERSION as pyserialver)\nfrom ..misc import hexdump\n\n\n__all__ = ['Serial']\n\n\nclass SerialExceptionWithErrno(SerialException):\n    \"\"\"Serial exception with errno extension\"\"\"\n\n    def __init__(self, message, errno=None):\n        SerialException.__init__(self, message)\n        self.errno = errno\n\n\nclass SocketSerial(SerialBase):\n    \"\"\"Fake serial port redirected to a Unix socket.\n\n       This is basically a copy of the serialposix serial port implementation\n       with redefined IO for a Unix socket\"\"\"\n\n    BACKEND = 'socket'\n    VIRTUAL_DEVICE = True\n\n    PYSERIAL_VERSION = tuple(int(x) for x in pyserialver.split('.'))\n\n    def _reconfigure_port(self):\n        pass\n\n    def open(self):\n        \"\"\"Open the initialized serial port\"\"\"\n        if self._port is None:\n            raise SerialException(\"Port must be configured before use.\")\n        if self.isOpen():\n            raise SerialException(\"Port is already open.\")\n        self._dump = False\n        self.sock = None\n        try:\n            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n            filename = self.portstr[self.portstr.index('://')+3:]\n            if filename.startswith('~/'):\n                home = os.getenv('HOME')\n                if home:\n                    filename = os.path.join(home, filename[2:])\n            self._filename = filename\n            self.sock.connect(self._filename)\n        except Exception as exc:\n            self.close()\n            msg = f'Could not open port: {exc}'\n            if isinstance(exc, socket.error):\n                # pylint: disable=no-member\n                raise SerialExceptionWithErrno(msg, exc.errno) from exc\n            raise SerialException(msg) from exc\n        self._set_open_state(True)\n        self._lastdtr = None\n\n    def close(self):\n        if self.sock:\n            try:\n                self.sock.shutdown(socket.SHUT_RDWR)\n            except Exception:\n                pass\n            try:\n                self.sock.close()\n            except Exception:\n                pass\n            self.sock = None\n        self._set_open_state(False)\n\n    def in_waiting(self):\n        \"\"\"Return the number of characters currently in the input buffer.\"\"\"\n        return 0\n\n    def read(self, size=1):\n        \"\"\"Read size bytes from the serial port. If a timeout is set it may\n           return less characters as requested. With no timeout it will block\n           until the requested number of bytes is read.\"\"\"\n        if self.sock is None:\n            raise PortNotOpenError\n        read = bytearray()\n        if size > 0:\n            while len(read) < size:\n                ready, _, _ = select.select([self.sock], [], [], self._timeout)\n                if not ready:\n                    break   # timeout\n                buf = self.sock.recv(size-len(read))\n                if not buf:\n                    # Some character is ready, but none can be read\n                    # it is a marker for a disconnected peer\n                    raise PortNotOpenError\n                read += buf\n                if self._timeout >= 0 and not buf:\n                    break  # early abort on timeout\n        return read\n\n    def write(self, data):\n        \"\"\"Output the given string over the serial port.\"\"\"\n        if self.sock is None:\n            raise PortNotOpenError\n        t = len(data)\n        d = data\n        while t > 0:\n            try:\n                if self.writeTimeout is not None and self.writeTimeout > 0:\n                    _, ready, _ = select.select([], [self.sock], [],\n                                                self.writeTimeout)\n                    if not ready:\n                        raise TimeoutError()\n                n = self.sock.send(d)\n                if self._dump:\n                    print(hexdump(d[:n]))\n                if self.writeTimeout is not None and self.writeTimeout > 0:\n                    _, ready, _ = select.select([], [self.sock], [],\n                                                self.writeTimeout)\n                    if not ready:\n                        raise TimeoutError()\n                d = d[n:]\n                t = t - n\n            except OSError as e:\n                if e.errno != errno.EAGAIN:\n                    raise\n\n    def flush(self):\n        \"\"\"Flush of file like objects. In this case, wait until all data\n           is written.\"\"\"\n\n    def reset_input_buffer(self):\n        \"\"\"Clear input buffer, discarding all that is in the buffer.\"\"\"\n\n    def reset_output_buffer(self):\n        \"\"\"Clear output buffer, aborting the current output and\n        discarding all that is in the buffer.\"\"\"\n\n    def send_break(self, duration=0.25):\n        \"\"\"Send break condition. Not supported\"\"\"\n\n    def _update_break_state(self):\n        \"\"\"Send break condition. Not supported\"\"\"\n\n    def _update_rts_state(self):\n        \"\"\"Set terminal status line: Request To Send\"\"\"\n\n    def _update_dtr_state(self):\n        \"\"\"Set terminal status line: Data Terminal Ready\"\"\"\n\n    def setDTR(self, value=1):\n        \"\"\"Set terminal status line: Data Terminal Ready\"\"\"\n\n    @property\n    def cts(self):\n        \"\"\"Read terminal status line: Clear To Send\"\"\"\n        return True\n\n    @property\n    def dsr(self):\n        \"\"\"Read terminal status line: Data Set Ready\"\"\"\n        return True\n\n    @property\n    def ri(self):\n        \"\"\"Read terminal status line: Ring Indicator\"\"\"\n        return False\n\n    @property\n    def cd(self):\n        \"\"\"Read terminal status line: Carrier Detect\"\"\"\n        return False\n\n    # - - platform specific - - - -\n\n    def nonblocking(self):\n        \"\"\"internal - not portable!\"\"\"\n        if self.sock is None:\n            raise PortNotOpenError\n        self.sock.setblocking(0)\n\n    def dump(self, enable):\n        self._dump = enable\n\n    # - - Helpers - -\n\n    def _set_open_state(self, open_):\n        if self.PYSERIAL_VERSION < (3, 0):\n            self._isOpen = bool(open_)\n        else:\n            self.is_open = bool(open_)\n\n\n# assemble Serial class with the platform specifc implementation and the base\n# for file-like behavior.\nclass Serial(SocketSerial, RawIOBase):\n    pass\n"
  },
  {
    "path": "pyftdi/serialext/tests/rl.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Check readline feature.\"\"\"\n\nfrom os.path import dirname\nfrom sys import path\nfrom serial import serial_for_url\n\npath.append(dirname(dirname(dirname(dirname(__file__)))))\n\n# pylint: disable=wrong-import-position\nfrom pyftdi import serialext\n\n\ndef main():\n    \"\"\"Verify readline() works with PyFTDI extension.\"\"\"\n    serialext.touch()\n    with serial_for_url('ftdi:///1', baudrate=115200) as ser:\n        line = ser.readline()\n        print(line)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/spi.py",
    "content": "# Copyright (c) 2010-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2016, Emmanuel Bouaziz <ebouaziz@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"SPI support for PyFdti\"\"\"\n\n# pylint: disable=invalid-name\n\nfrom logging import getLogger\nfrom struct import calcsize as scalc, pack as spack, unpack as sunpack\nfrom threading import Lock\nfrom typing import Any, Iterable, Mapping, Optional, Set, Union\nfrom usb.core import Device as UsbDevice\nfrom .ftdi import Ftdi, FtdiError\n\n\nclass SpiIOError(FtdiError):\n    \"\"\"SPI I/O error\"\"\"\n\n\nclass SpiPort:\n    \"\"\"SPI port\n\n       An SPI port is never instanciated directly: use\n       :py:meth:`SpiController.get_port()` method to obtain an SPI port.\n\n       Example:\n\n       >>> ctrl = SpiController()\n       >>> ctrl.configure('ftdi://ftdi:232h/1')\n       >>> spi = ctrl.get_port(1)\n       >>> spi.set_frequency(1000000)\n       >>> # send 2 bytes\n       >>> spi.exchange([0x12, 0x34])\n       >>> # send 2 bytes, then receive 2 bytes\n       >>> out = spi.exchange([0x12, 0x34], 2)\n       >>> # send 2 bytes, then receive 4 bytes, manage the transaction\n       >>> out = spi.exchange([0x12, 0x34], 2, True, False)\n       >>> out.extend(spi.exchange([], 2, False, True))\n    \"\"\"\n\n    def __init__(self, controller: 'SpiController', cs: int, cs_hold: int = 3,\n                 spi_mode: int = 0):\n        self.log = getLogger('pyftdi.spi.port')\n        self._controller = controller\n        self._frequency = self._controller.frequency\n        self._cs = cs\n        self._cs_hold = cs_hold\n        self.set_mode(spi_mode)\n\n    def exchange(self, out: Union[bytes, bytearray, Iterable[int]] = b'',\n                 readlen: int = 0, start: bool = True, stop: bool = True,\n                 duplex: bool = False, droptail: int = 0) -> bytes:\n        \"\"\"Perform an exchange or a transaction with the SPI slave\n\n           :param out: data to send to the SPI slave, may be empty to read out\n                       data from the slave with no write.\n           :param readlen: count of bytes to read out from the slave,\n                       may be zero to only write to the slave\n           :param start: whether to start an SPI transaction, i.e.\n                        activate the /CS line for the slave. Use False to\n                        resume a previously started transaction\n           :param stop: whether to desactivete the /CS line for the slave.\n                       Use False if the transaction should complete with a\n                       further call to exchange()\n           :param duplex: perform a full-duplex exchange (vs. half-duplex),\n                          i.e. bits are clocked in and out at once.\n           :param droptail: ignore up to 7 last bits (for non-byte sized SPI\n                               accesses)\n           :return: an array of bytes containing the data read out from the\n                    slave\n        \"\"\"\n        return self._controller.exchange(self._frequency, out, readlen,\n                                         start and self._cs_prolog,\n                                         stop and self._cs_epilog,\n                                         self._cpol, self._cpha,\n                                         duplex, droptail)\n\n    def read(self, readlen: int = 0, start: bool = True, stop: bool = True,\n             droptail: int = 0) -> bytes:\n        \"\"\"Read out bytes from the slave\n\n           :param readlen: count of bytes to read out from the slave,\n                           may be zero to only write to the slave\n           :param start: whether to start an SPI transaction, i.e.\n                        activate the /CS line for the slave. Use False to\n                        resume a previously started transaction\n           :param stop: whether to desactivete the /CS line for the slave.\n                       Use False if the transaction should complete with a\n                       further call to exchange()\n           :param droptail: ignore up to 7 last bits (for non-byte sized SPI\n                               accesses)\n           :return: an array of bytes containing the data read out from the\n                    slave\n        \"\"\"\n        return self._controller.exchange(self._frequency, [], readlen,\n                                         start and self._cs_prolog,\n                                         stop and self._cs_epilog,\n                                         self._cpol, self._cpha, False,\n                                         droptail)\n\n    def write(self, out: Union[bytes, bytearray, Iterable[int]],\n              start: bool = True, stop: bool = True, droptail: int = 0) \\\n            -> None:\n        \"\"\"Write bytes to the slave\n\n           :param out: data to send to the SPI slave, may be empty to read out\n                       data from the slave with no write.\n           :param start: whether to start an SPI transaction, i.e.\n                        activate the /CS line for the slave. Use False to\n                        resume a previously started transaction\n           :param stop: whether to desactivete the /CS line for the slave.\n                        Use False if the transaction should complete with a\n                        further call to exchange()\n           :param droptail: ignore up to 7 last bits (for non-byte sized SPI\n                               accesses)\n        \"\"\"\n        return self._controller.exchange(self._frequency, out, 0,\n                                         start and self._cs_prolog,\n                                         stop and self._cs_epilog,\n                                         self._cpol, self._cpha, False,\n                                         droptail)\n\n    def flush(self) -> None:\n        \"\"\"Force the flush of the HW FIFOs\"\"\"\n        self._controller.flush()\n\n    def set_frequency(self, frequency: float):\n        \"\"\"Change SPI bus frequency\n\n           :param float frequency: the new frequency in Hz\n        \"\"\"\n        self._frequency = min(frequency, self._controller.frequency_max)\n\n    def set_mode(self, mode: int, cs_hold: Optional[int] = None) -> None:\n        \"\"\"Set or change the SPI mode to communicate with the SPI slave.\n\n           :param mode: new SPI mode\n           :param cs_hold: change the /CS hold duration (or keep using previous\n                           value)\n        \"\"\"\n        if not 0 <= mode <= 3:\n            raise SpiIOError(f'Invalid SPI mode: {mode}')\n        if (mode & 0x2) and not self._controller.is_inverted_cpha_supported:\n            raise SpiIOError('SPI with CPHA high is not supported by '\n                             'this FTDI device')\n        if cs_hold is None:\n            cs_hold = self._cs_hold\n        else:\n            self._cs_hold = cs_hold\n        self._cpol = bool(mode & 0x2)\n        self._cpha = bool(mode & 0x1)\n        cs_clock = 0xFF & ~((int(not self._cpol) and SpiController.SCK_BIT) |\n                            SpiController.DO_BIT)\n        cs_select = 0xFF & ~((SpiController.CS_BIT << self._cs) |\n                             (int(not self._cpol) and SpiController.SCK_BIT) |\n                             SpiController.DO_BIT)\n        self._cs_prolog = bytes([cs_clock, cs_select])\n        self._cs_epilog = bytes([cs_select] + [cs_clock] * int(cs_hold))\n\n    def force_select(self, level: Optional[bool] = None,\n                     cs_hold: float = 0) -> None:\n        \"\"\"Force-drive /CS signal.\n\n           This API is not designed for a regular usage, but is reserved to\n           very specific slave devices that require non-standard SPI\n           signalling. There are very few use cases where this API is required.\n\n           :param level: level to force on /CS output. This is a tri-state\n                         value. A boolean value forces the selected signal\n                         level; note that SpiPort no longer enforces that\n                         following API calls generates valid SPI signalling:\n                         use with extreme care. `None` triggers a pulse on /CS\n                         output, i.e. /CS is not asserted once the method\n                         returns, whatever the actual /CS level when this API\n                         is called.\n           :param cs_hold: /CS hold duration, as a unitless value. It is not\n                         possible to control the exact duration of the pulse,\n                         as it depends on the USB bus and the FTDI frequency.\n        \"\"\"\n        clk, sel = self._cs_prolog\n        if cs_hold:\n            hold = max(1, cs_hold)\n            if hold > SpiController.PAYLOAD_MAX_LENGTH:\n                raise ValueError('cs_hold is too long')\n        else:\n            hold = self._cs_hold\n        if level is None:\n            seq = bytearray([clk])\n            seq.extend([sel]*(1+hold))\n            seq.extend([clk]*self._cs_hold)\n        elif level:\n            seq = bytearray([clk] * hold)\n        else:\n            seq = bytearray([clk] * hold)\n            seq.extend([sel]*(1+hold))\n        self._controller.force_control(self._frequency, bytes(seq))\n\n    @property\n    def frequency(self) -> float:\n        \"\"\"Return the current SPI bus block\"\"\"\n        return self._frequency\n\n    @property\n    def cs(self) -> int:\n        \"\"\"Return the /CS index.\n\n          :return: the /CS index (starting from 0)\n        \"\"\"\n        return self._cs\n\n    @property\n    def mode(self) -> int:\n        \"\"\"Return the current SPI mode.\n\n           :return: the SPI mode\n        \"\"\"\n        return (int(self._cpol) << 2) | int(self._cpha)\n\n\nclass SpiGpioPort:\n    \"\"\"GPIO port\n\n       A SpiGpioPort instance enables to drive GPIOs wich are not reserved for\n       SPI feature as regular GPIOs.\n\n       GPIO are managed as a bitfield. The LSBs are reserved for the SPI\n       feature, which means that the lowest pin that can be used as a GPIO is\n       *b4*:\n\n       * *b0*: SPI SCLK\n       * *b1*: SPI MOSI\n       * *b2*: SPI MISO\n       * *b3*: SPI CS0\n       * *b4*: SPI CS1 or first GPIO\n\n       If more than one SPI device is used, less GPIO pins are available, see\n       the cs_count argument of the SpiController constructor.\n\n       There is no offset bias in GPIO bit position, *i.e.* the first available\n       GPIO can be reached from as ``0x10``.\n\n       Bitfield size depends on the FTDI device: 4432H series use 8-bit GPIO\n       ports, while 232H and 2232H series use wide 16-bit ports.\n\n       An SpiGpio port is never instanciated directly: use\n       :py:meth:`SpiController.get_gpio()` method to obtain the GPIO port.\n    \"\"\"\n    def __init__(self, controller: 'SpiController'):\n        self.log = getLogger('pyftdi.spi.gpio')\n        self._controller = controller\n\n    @property\n    def pins(self) -> int:\n        \"\"\"Report the configured GPIOs as a bitfield.\n\n           A true bit represents a GPIO, a false bit a reserved or not\n           configured pin.\n\n           :return: the bitfield of configured GPIO pins.\n        \"\"\"\n        return self._controller.gpio_pins\n\n    @property\n    def all_pins(self) -> int:\n        \"\"\"Report the addressable GPIOs as a bitfield.\n\n           A true bit represents a pin which may be used as a GPIO, a false bit\n           a reserved pin (for SPI support)\n\n           :return: the bitfield of configurable GPIO pins.\n        \"\"\"\n        return self._controller.gpio_all_pins\n\n    @property\n    def width(self) -> int:\n        \"\"\"Report the FTDI count of addressable pins.\n\n           Note that all pins, including reserved SPI ones, are reported.\n\n           :return: the count of IO pins (including SPI ones).\n        \"\"\"\n        return self._controller.width\n\n    @property\n    def direction(self) -> int:\n        \"\"\"Provide the FTDI GPIO direction.self\n\n           A true bit represents an output GPIO, a false bit an input GPIO.\n\n           :return: the bitfield of direction.\n        \"\"\"\n        return self._controller.direction\n\n    def read(self, with_output: bool = False) -> int:\n        \"\"\"Read GPIO port.\n\n           :param with_output: set to unmask output pins\n           :return: the GPIO port pins as a bitfield\n        \"\"\"\n        return self._controller.read_gpio(with_output)\n\n    def write(self, value: int) -> None:\n        \"\"\"Write GPIO port.\n\n           :param value: the GPIO port pins as a bitfield\n        \"\"\"\n        return self._controller.write_gpio(value)\n\n    def set_direction(self, pins: int, direction: int) -> None:\n        \"\"\"Change the direction of the GPIO pins.\n\n           :param pins: which GPIO pins should be reconfigured\n           :param direction: direction bitfield (high level for output)\n        \"\"\"\n        self._controller.set_gpio_direction(pins, direction)\n\n\nclass SpiController:\n    \"\"\"SPI master.\n\n        :param int cs_count: is the number of /CS lines (one per device to\n            drive on the SPI bus)\n        :param turbo: increase throughput over USB bus, but may not be\n                      supported with some specific slaves\n    \"\"\"\n\n    SCK_BIT = 0x01\n    DO_BIT = 0x02\n    DI_BIT = 0x04\n    CS_BIT = 0x08\n    SPI_BITS = DI_BIT | DO_BIT | SCK_BIT\n    PAYLOAD_MAX_LENGTH = 0xFF00  # 16 bits max (- spare for control)\n\n    def __init__(self, cs_count: int = 1, turbo: bool = True):\n        self.log = getLogger('pyftdi.spi.ctrl')\n        self._ftdi = Ftdi()\n        self._lock = Lock()\n        self._gpio_port = None\n        self._gpio_dir = 0\n        self._gpio_mask = 0\n        self._gpio_low = 0\n        self._wide_port = False\n        self._cs_count = cs_count\n        self._turbo = turbo\n        self._immediate = bytes((Ftdi.SEND_IMMEDIATE,))\n        self._frequency = 0.0\n        self._clock_phase = False\n        self._cs_bits = 0\n        self._spi_ports = []\n        self._spi_dir = 0\n        self._spi_mask = self.SPI_BITS\n\n    def configure(self, url: Union[str, UsbDevice],\n                  **kwargs: Mapping[str, Any]) -> None:\n        \"\"\"Configure the FTDI interface as a SPI master\n\n           :param url: FTDI URL string, such as ``ftdi://ftdi:232h/1``\n           :param kwargs: options to configure the SPI bus\n\n           Accepted options:\n\n           * ``interface``: when URL is specifed as a USB device, the interface\n             named argument can be used to select a specific port of the FTDI\n             device, as an integer starting from 1.\n           * ``direction`` a bitfield specifying the FTDI GPIO direction,\n             where high level defines an output, and low level defines an\n             input. Only useful to setup default IOs at start up, use\n             :py:class:`SpiGpioPort` to drive GPIOs. Note that pins reserved\n             for SPI feature take precedence over any this setting.\n           * ``initial`` a bitfield specifying the initial output value. Only\n             useful to setup default IOs at start up, use\n             :py:class:`SpiGpioPort` to drive GPIOs.\n           * ``frequency`` the SPI bus frequency in Hz. Note that each slave\n             may reconfigure the SPI bus with a specialized\n             frequency.\n           * ``cs_count`` count of chip select signals dedicated to select\n             SPI slave devices, starting from A*BUS3 pin\n           * ``turbo`` whether to enable or disable turbo mode\n           * ``debug`` to increase log verbosity, using MPSSE tracer\n        \"\"\"\n        # it is better to specify CS and turbo in configure, but the older\n        # API where these parameters are specified at instanciation has been\n        # preserved\n        if 'cs_count' in kwargs:\n            self._cs_count = int(kwargs['cs_count'])\n            del kwargs['cs_count']\n        if not 1 <= self._cs_count <= 5:\n            raise ValueError(f'Unsupported CS line count: {self._cs_count}')\n        if 'turbo' in kwargs:\n            self._turbo = bool(kwargs['turbo'])\n            del kwargs['turbo']\n        if 'direction' in kwargs:\n            io_dir = int(kwargs['direction'])\n            del kwargs['direction']\n        else:\n            io_dir = 0\n        if 'initial' in kwargs:\n            io_out = int(kwargs['initial'])\n            del kwargs['initial']\n        else:\n            io_out = 0\n        if 'interface' in kwargs:\n            if isinstance(url, str):\n                raise SpiIOError('url and interface are mutually exclusive')\n            interface = int(kwargs['interface'])\n            del kwargs['interface']\n        else:\n            interface = 1\n        with self._lock:\n            if self._frequency > 0.0:\n                raise SpiIOError('Already configured')\n            self._cs_bits = (((SpiController.CS_BIT << self._cs_count) - 1) &\n                             ~(SpiController.CS_BIT - 1))\n            self._spi_ports = [None] * self._cs_count\n            self._spi_dir = (self._cs_bits |\n                             SpiController.DO_BIT |\n                             SpiController.SCK_BIT)\n            self._spi_mask = self._cs_bits | self.SPI_BITS\n            # until the device is open, there is no way to tell if it has a\n            # wide (16) or narrow port (8). Lower API can deal with any, so\n            # delay any truncation till the device is actually open\n            self._set_gpio_direction(16, (~self._spi_mask) & 0xFFFF, io_dir)\n            kwargs['direction'] = self._spi_dir | self._gpio_dir\n            kwargs['initial'] = self._cs_bits | (io_out & self._gpio_mask)\n            if not isinstance(url, str):\n                self._frequency = self._ftdi.open_mpsse_from_device(\n                    url, interface=interface, **kwargs)\n            else:\n                self._frequency = self._ftdi.open_mpsse_from_url(url, **kwargs)\n            self._ftdi.enable_adaptive_clock(False)\n            self._wide_port = self._ftdi.has_wide_port\n            if not self._wide_port:\n                self._set_gpio_direction(8, io_out & 0xFF, io_dir & 0xFF)\n\n    def close(self, freeze: bool = False) -> None:\n        \"\"\"Close the FTDI interface.\n\n           :param freeze: if set, FTDI port is not reset to its default\n                          state on close.\n        \"\"\"\n        with self._lock:\n            if self._ftdi.is_connected:\n                self._ftdi.close(freeze)\n        self._frequency = 0.0\n\n    def terminate(self) -> None:\n        \"\"\"Close the FTDI interface.\n\n           :note: deprecated API, use close()\n        \"\"\"\n        self.close()\n\n    def get_port(self, cs: int, freq: Optional[float] = None,\n                 mode: int = 0) -> SpiPort:\n        \"\"\"Obtain a SPI port to drive a SPI device selected by Chip Select.\n\n           :note: SPI mode 1 and 3 are not officially supported.\n\n           :param cs: chip select slot, starting from 0\n           :param freq: SPI bus frequency for this slave in Hz\n           :param mode: SPI mode [0, 1, 2, 3]\n        \"\"\"\n        with self._lock:\n            if not self._ftdi.is_connected:\n                raise SpiIOError(\"FTDI controller not initialized\")\n            if cs >= len(self._spi_ports):\n                if cs < 5:\n                    # increase cs_count (up to 4) to reserve more /CS channels\n                    raise SpiIOError('/CS pin {cs} not reserved for SPI')\n                raise SpiIOError(f'No such SPI port: {cs}')\n            if not self._spi_ports[cs]:\n                freq = min(freq or self._frequency, self.frequency_max)\n                hold = freq and (1+int(1E6/freq))\n                self._spi_ports[cs] = SpiPort(self, cs, cs_hold=hold,\n                                              spi_mode=mode)\n                self._spi_ports[cs].set_frequency(freq)\n                self._flush()\n            return self._spi_ports[cs]\n\n    def get_gpio(self) -> SpiGpioPort:\n        \"\"\"Retrieve the GPIO port.\n\n           :return: GPIO port\n        \"\"\"\n        with self._lock:\n            if not self._ftdi.is_connected:\n                raise SpiIOError(\"FTDI controller not initialized\")\n            if not self._gpio_port:\n                self._gpio_port = SpiGpioPort(self)\n            return self._gpio_port\n\n    @property\n    def ftdi(self) -> Ftdi:\n        \"\"\"Return the Ftdi instance.\n\n           :return: the Ftdi instance\n        \"\"\"\n        return self._ftdi\n\n    @property\n    def configured(self) -> bool:\n        \"\"\"Test whether the device has been properly configured.\n\n           :return: True if configured\n        \"\"\"\n        return self._ftdi.is_connected\n\n    @property\n    def frequency_max(self) -> float:\n        \"\"\"Provides the maximum SPI clock frequency in Hz.\n\n           :return: SPI bus clock frequency\n        \"\"\"\n        return self._ftdi.frequency_max\n\n    @property\n    def frequency(self) -> float:\n        \"\"\"Provides the current SPI clock frequency in Hz.\n\n           :return: the SPI bus clock frequency\n        \"\"\"\n        return self._frequency\n\n    @property\n    def direction(self):\n        \"\"\"Provide the FTDI pin direction\n\n           A true bit represents an output pin, a false bit an input pin.\n\n           :return: the bitfield of direction.\n        \"\"\"\n        return self._spi_dir | self._gpio_dir\n\n    @property\n    def channels(self) -> int:\n        \"\"\"Provide the maximum count of slaves.\n\n\n           :return: the count of pins reserved to drive the /CS signal\n        \"\"\"\n        return self._cs_count\n\n    @property\n    def active_channels(self) -> Set[int]:\n        \"\"\"Provide the set of configured slaves /CS.\n\n           :return: Set of /CS, one for each configured slaves\n        \"\"\"\n        return {port[0] for port in enumerate(self._spi_ports) if port[1]}\n\n    @property\n    def gpio_pins(self):\n        \"\"\"Report the configured GPIOs as a bitfield.\n\n           A true bit represents a GPIO, a false bit a reserved or not\n           configured pin.\n\n           :return: the bitfield of configured GPIO pins.\n        \"\"\"\n        with self._lock:\n            return self._gpio_mask\n\n    @property\n    def gpio_all_pins(self):\n        \"\"\"Report the addressable GPIOs as a bitfield.\n\n           A true bit represents a pin which may be used as a GPIO, a false bit\n           a reserved pin (for SPI support)\n\n           :return: the bitfield of configurable GPIO pins.\n        \"\"\"\n        mask = (1 << self.width) - 1\n        with self._lock:\n            return mask & ~self._spi_mask\n\n    @property\n    def width(self):\n        \"\"\"Report the FTDI count of addressable pins.\n\n           :return: the count of IO pins (including SPI ones).\n        \"\"\"\n        return 16 if self._wide_port else 8\n\n    @property\n    def is_inverted_cpha_supported(self) -> bool:\n        \"\"\"Report whether it is possible to supported CPHA=1.\n\n           :return: inverted CPHA supported (with a kludge)\n        \"\"\"\n        return self._ftdi.is_H_series\n\n    def exchange(self, frequency: float,\n                 out: Union[bytes, bytearray, Iterable[int]], readlen: int,\n                 cs_prolog: Optional[bytes] = None,\n                 cs_epilog: Optional[bytes] = None,\n                 cpol: bool = False, cpha: bool = False,\n                 duplex: bool = False, droptail: int = 0) -> bytes:\n        \"\"\"Perform an exchange or a transaction with the SPI slave\n\n           :param out: data to send to the SPI slave, may be empty to read out\n                       data from the slave with no write.\n           :param readlen: count of bytes to read out from the slave,\n                           may be zero to only write to the slave,\n           :param cs_prolog: the prolog MPSSE command sequence to execute\n                             before the actual exchange.\n           :param cs_epilog: the epilog MPSSE command sequence to execute\n                             after the actual exchange.\n           :param cpol: SPI clock polarity, derived from the SPI mode\n           :param cpol: SPI clock phase, derived from the SPI mode\n           :param duplex: perform a full-duplex exchange (vs. half-duplex),\n                          i.e. bits are clocked in and out at once or\n                          in a write-then-read manner.\n           :param droptail: ignore up to 7 last bits (for non-byte sized SPI\n                             accesses)\n           :return: bytes containing the data read out from the slave, if any\n        \"\"\"\n        if not 0 <= droptail <= 7:\n            raise ValueError('Invalid skip bit count')\n        if duplex:\n            if readlen > len(out):\n                tmp = bytearray(out)\n                tmp.extend([0] * (readlen - len(out)))\n                out = tmp\n            elif not readlen:\n                readlen = len(out)\n        with self._lock:\n            if duplex:\n                data = self._exchange_full_duplex(frequency, out,\n                                                  cs_prolog, cs_epilog,\n                                                  cpol, cpha, droptail)\n                return data[:readlen]\n            return self._exchange_half_duplex(frequency, out, readlen,\n                                              cs_prolog, cs_epilog,\n                                              cpol, cpha, droptail)\n\n    def force_control(self, frequency: float, sequence: bytes) -> None:\n        \"\"\"Execution an arbitrary SPI control bit sequence.\n           Use with extreme care, as it may lead to unexpected results. Regular\n           usage of SPI does not require to invoke this API.\n\n           :param sequence: the bit sequence to execute.\n        \"\"\"\n        with self._lock:\n            self._force(frequency, sequence)\n\n    def flush(self) -> None:\n        \"\"\"Flush the HW FIFOs.\n        \"\"\"\n        with self._lock:\n            self._flush()\n\n    def read_gpio(self, with_output: bool = False) -> int:\n        \"\"\"Read GPIO port\n\n           :param  with_output: set to unmask output pins\n           :return: the GPIO port pins as a bitfield\n        \"\"\"\n        with self._lock:\n            data = self._read_raw(self._wide_port)\n        value = data & self._gpio_mask\n        if not with_output:\n            value &= ~self._gpio_dir\n        return value\n\n    def write_gpio(self, value: int) -> None:\n        \"\"\"Write GPIO port\n\n           :param value: the GPIO port pins as a bitfield\n        \"\"\"\n        with self._lock:\n            if (value & self._gpio_dir) != value:\n                raise SpiIOError(f'No such GPO pins: '\n                                 f'{self._gpio_dir:04x}/{value:04x}')\n            # perform read-modify-write\n            use_high = self._wide_port and (self.direction & 0xff00)\n            data = self._read_raw(use_high)\n            data &= ~self._gpio_mask\n            data |= value\n            self._write_raw(data, use_high)\n            self._gpio_low = data & 0xFF & ~self._spi_mask\n\n    def set_gpio_direction(self, pins: int, direction: int) -> None:\n        \"\"\"Change the direction of the GPIO pins\n\n           :param pins: which GPIO pins should be reconfigured\n           :param direction: direction bitfield (on for output)\n        \"\"\"\n        with self._lock:\n            self._set_gpio_direction(16 if self._wide_port else 8,\n                                     pins, direction)\n\n    def _set_gpio_direction(self, width: int, pins: int,\n                            direction: int) -> None:\n        if pins & self._spi_mask:\n            raise SpiIOError('Cannot access SPI pins as GPIO')\n        gpio_mask = (1 << width) - 1\n        gpio_mask &= ~self._spi_mask\n        if (pins & gpio_mask) != pins:\n            raise SpiIOError('No such GPIO pin(s)')\n        self._gpio_dir &= ~pins\n        self._gpio_dir |= (pins & direction)\n        self._gpio_mask = gpio_mask & pins\n\n    def _read_raw(self, read_high: bool) -> int:\n        if not self._ftdi.is_connected:\n            raise SpiIOError(\"FTDI controller not initialized\")\n        if read_high:\n            cmd = bytes([Ftdi.GET_BITS_LOW,\n                         Ftdi.GET_BITS_HIGH,\n                         Ftdi.SEND_IMMEDIATE])\n            fmt = '<H'\n        else:\n            cmd = bytes([Ftdi.GET_BITS_LOW,\n                         Ftdi.SEND_IMMEDIATE])\n            fmt = 'B'\n        self._ftdi.write_data(cmd)\n        size = scalc(fmt)\n        data = self._ftdi.read_data_bytes(size, 4)\n        if len(data) != size:\n            raise SpiIOError('Cannot read GPIO')\n        value, = sunpack(fmt, data)\n        return value\n\n    def _write_raw(self, data: int, write_high: bool) -> None:\n        if not self._ftdi.is_connected:\n            raise SpiIOError(\"FTDI controller not initialized\")\n        direction = self.direction\n        low_data = data & 0xFF\n        low_dir = direction & 0xFF\n        if write_high:\n            high_data = (data >> 8) & 0xFF\n            high_dir = (direction >> 8) & 0xFF\n            cmd = bytes([Ftdi.SET_BITS_LOW, low_data, low_dir,\n                         Ftdi.SET_BITS_HIGH, high_data, high_dir])\n        else:\n            cmd = bytes([Ftdi.SET_BITS_LOW, low_data, low_dir])\n        self._ftdi.write_data(cmd)\n\n    def _force(self, frequency: float, sequence: bytes):\n        if not self._ftdi.is_connected:\n            raise SpiIOError(\"FTDI controller not initialized\")\n        if len(sequence) > SpiController.PAYLOAD_MAX_LENGTH:\n            raise SpiIOError(\"Output payload is too large\")\n        if self._frequency != frequency:\n            self._ftdi.set_frequency(frequency)\n            # store the requested value, not the actual one (best effort),\n            # to avoid setting unavailable values on each call.\n            self._frequency = frequency\n        cmd = bytearray()\n        direction = self.direction & 0xFF\n        for ctrl in sequence:\n            ctrl &= self._spi_mask\n            ctrl |= self._gpio_low\n            cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction))\n        self._ftdi.write_data(cmd)\n\n    def _exchange_half_duplex(self, frequency: float,\n                              out: Union[bytes, bytearray, Iterable[int]],\n                              readlen: int, cs_prolog: bytes, cs_epilog: bytes,\n                              cpol: bool, cpha: bool,\n                              droptail: int) -> bytes:\n        if not self._ftdi.is_connected:\n            raise SpiIOError(\"FTDI controller not initialized\")\n        if len(out) > SpiController.PAYLOAD_MAX_LENGTH:\n            raise SpiIOError(\"Output payload is too large\")\n        if readlen > SpiController.PAYLOAD_MAX_LENGTH:\n            raise SpiIOError(\"Input payload is too large\")\n        if cpha:\n            # to enable CPHA, we need to use a workaround with FTDI device,\n            # that is enable 3-phase clocking (which is usually dedicated to\n            # I2C support). This mode use use 3 clock period instead of 2,\n            # which implies the FTDI frequency should be fixed to match the\n            # requested one.\n            frequency = (3*frequency)//2\n        if self._frequency != frequency:\n            self._ftdi.set_frequency(frequency)\n            # store the requested value, not the actual one (best effort),\n            # to avoid setting unavailable values on each call.\n            self._frequency = frequency\n        direction = self.direction & 0xFF  # low bits only\n        cmd = bytearray()\n        for ctrl in cs_prolog or []:\n            ctrl &= self._spi_mask\n            ctrl |= self._gpio_low\n            cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction))\n        epilog = bytearray()\n        if cs_epilog:\n            for ctrl in cs_epilog:\n                ctrl &= self._spi_mask\n                ctrl |= self._gpio_low\n                epilog.extend((Ftdi.SET_BITS_LOW, ctrl, direction))\n            # Restore idle state\n            cs_high = [Ftdi.SET_BITS_LOW, self._cs_bits | self._gpio_low,\n                       direction]\n            if not self._turbo:\n                cs_high.append(Ftdi.SEND_IMMEDIATE)\n            epilog.extend(cs_high)\n        writelen = len(out)\n        if self._clock_phase != cpha:\n            self._ftdi.enable_3phase_clock(cpha)\n            self._clock_phase = cpha\n        if writelen:\n            if not droptail:\n                wcmd = (Ftdi.WRITE_BYTES_NVE_MSB if not cpol else\n                        Ftdi.WRITE_BYTES_PVE_MSB)\n                write_cmd = spack('<BH', wcmd, writelen-1)\n                cmd.extend(write_cmd)\n                cmd.extend(out)\n            else:\n                bytelen = writelen-1\n                if bytelen:\n                    wcmd = (Ftdi.WRITE_BYTES_NVE_MSB if not cpol else\n                            Ftdi.WRITE_BYTES_PVE_MSB)\n                    write_cmd = spack('<BH', wcmd, bytelen-1)\n                    cmd.extend(write_cmd)\n                    cmd.extend(out[:-1])\n                wcmd = (Ftdi.WRITE_BITS_NVE_MSB if not cpol else\n                        Ftdi.WRITE_BITS_PVE_MSB)\n                write_cmd = spack('<BBB', wcmd, 7-droptail, out[-1])\n                cmd.extend(write_cmd)\n        if readlen:\n            if not droptail:\n                rcmd = (Ftdi.READ_BYTES_NVE_MSB if not cpol else\n                        Ftdi.READ_BYTES_PVE_MSB)\n                read_cmd = spack('<BH', rcmd, readlen-1)\n                cmd.extend(read_cmd)\n            else:\n                bytelen = readlen-1\n                if bytelen:\n                    rcmd = (Ftdi.READ_BYTES_NVE_MSB if not cpol else\n                            Ftdi.READ_BYTES_PVE_MSB)\n                    read_cmd = spack('<BH', rcmd, bytelen-1)\n                    cmd.extend(read_cmd)\n                rcmd = (Ftdi.READ_BITS_NVE_MSB if not cpol else\n                        Ftdi.READ_BITS_PVE_MSB)\n                read_cmd = spack('<BB', rcmd, 7-droptail)\n                cmd.extend(read_cmd)\n            cmd.extend(self._immediate)\n            if self._turbo:\n                if epilog:\n                    cmd.extend(epilog)\n                self._ftdi.write_data(cmd)\n            else:\n                self._ftdi.write_data(cmd)\n                if epilog:\n                    self._ftdi.write_data(epilog)\n            # USB read cycle may occur before the FTDI device has actually\n            # sent the data, so try to read more than once if no data is\n            # actually received\n            data = self._ftdi.read_data_bytes(readlen, 4)\n            if droptail:\n                data[-1] = 0xff & (data[-1] << droptail)\n        else:\n            if writelen:\n                if self._turbo:\n                    if epilog:\n                        cmd.extend(epilog)\n                    self._ftdi.write_data(cmd)\n                else:\n                    self._ftdi.write_data(cmd)\n                    if epilog:\n                        self._ftdi.write_data(epilog)\n            data = bytearray()\n        return data\n\n    def _exchange_full_duplex(self, frequency: float,\n                              out: Union[bytes, bytearray, Iterable[int]],\n                              cs_prolog: bytes, cs_epilog: bytes,\n                              cpol: bool, cpha: bool,\n                              droptail: int) -> bytes:\n        if not self._ftdi.is_connected:\n            raise SpiIOError(\"FTDI controller not initialized\")\n        if len(out) > SpiController.PAYLOAD_MAX_LENGTH:\n            raise SpiIOError(\"Output payload is too large\")\n        if cpha:\n            # to enable CPHA, we need to use a workaround with FTDI device,\n            # that is enable 3-phase clocking (which is usually dedicated to\n            # I2C support). This mode use use 3 clock period instead of 2,\n            # which implies the FTDI frequency should be fixed to match the\n            # requested one.\n            frequency = (3*frequency)//2\n        if self._frequency != frequency:\n            self._ftdi.set_frequency(frequency)\n            # store the requested value, not the actual one (best effort),\n            # to avoid setting unavailable values on each call.\n            self._frequency = frequency\n        direction = self.direction & 0xFF  # low bits only\n        cmd = bytearray()\n        for ctrl in cs_prolog or []:\n            ctrl &= self._spi_mask\n            ctrl |= self._gpio_low\n            cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction))\n        epilog = bytearray()\n        if cs_epilog:\n            for ctrl in cs_epilog:\n                ctrl &= self._spi_mask\n                ctrl |= self._gpio_low\n                epilog.extend((Ftdi.SET_BITS_LOW, ctrl, direction))\n            # Restore idle state\n            cs_high = [Ftdi.SET_BITS_LOW, self._cs_bits | self._gpio_low,\n                       direction]\n            if not self._turbo:\n                cs_high.append(Ftdi.SEND_IMMEDIATE)\n            epilog.extend(cs_high)\n        exlen = len(out)\n        if self._clock_phase != cpha:\n            self._ftdi.enable_3phase_clock(cpha)\n            self._clock_phase = cpha\n        if not droptail:\n            wcmd = (Ftdi.RW_BYTES_PVE_NVE_MSB if not cpol else\n                    Ftdi.RW_BYTES_NVE_PVE_MSB)\n            write_cmd = spack('<BH', wcmd, exlen-1)\n            cmd.extend(write_cmd)\n            cmd.extend(out)\n        else:\n            bytelen = exlen-1\n            if bytelen:\n                wcmd = (Ftdi.RW_BYTES_PVE_NVE_MSB if not cpol else\n                        Ftdi.RW_BYTES_NVE_PVE_MSB)\n                write_cmd = spack('<BH', wcmd, bytelen-1)\n                cmd.extend(write_cmd)\n                cmd.extend(out[:-1])\n            wcmd = (Ftdi.RW_BITS_PVE_NVE_MSB if not cpol else\n                    Ftdi.RW_BITS_NVE_PVE_MSB)\n            write_cmd = spack('<BBB', wcmd, 7-droptail, out[-1])\n            cmd.extend(write_cmd)\n        cmd.extend(self._immediate)\n        if self._turbo:\n            if epilog:\n                cmd.extend(epilog)\n            self._ftdi.write_data(cmd)\n        else:\n            self._ftdi.write_data(cmd)\n            if epilog:\n                self._ftdi.write_data(epilog)\n        # USB read cycle may occur before the FTDI device has actually\n        # sent the data, so try to read more than once if no data is\n        # actually received\n        data = self._ftdi.read_data_bytes(exlen, 4)\n        if droptail:\n            data[-1] = 0xff & (data[-1] << droptail)\n        return data\n\n    def _flush(self) -> None:\n        self._ftdi.write_data(self._immediate)\n        self._ftdi.purge_buffers()\n"
  },
  {
    "path": "pyftdi/term.py",
    "content": "\"\"\"Terminal management helpers\"\"\"\n\n# Copyright (c) 2020-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2020, Michael Pratt <mpratt51@gmail.com>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nfrom os import environ, read as os_read\nfrom sys import platform, stderr, stdin, stdout\n\n# pylint: disable=import-error\nif platform == 'win32':\n    import msvcrt\n    from subprocess import call  # ugly workaround for an ugly OS\nelse:\n    from termios import (ECHO, ICANON, TCSAFLUSH, TCSANOW, VINTR, VMIN, VSUSP,\n                         VTIME, tcgetattr, tcsetattr)\n\n    # pylint workaround (disable=used-before-assignment)\n    def call():\n        # pylint: disable=missing-function-docstring\n        pass\n\n\nclass Terminal:\n    \"\"\"Terminal management function\n    \"\"\"\n\n    FNKEYS = {\n        # Ctrl + Alt + Backspace\n        14:     b'\\x1b^H',\n        # Ctrl + Alt + Enter\n        28:     b'\\x1b\\r',\n        # Pause/Break\n        29:     b'\\x1c',\n        # Arrows\n        72:     b'\\x1b[A',\n        80:     b'\\x1b[B',\n        77:     b'\\x1b[C',\n        75:     b'\\x1b[D',\n        # Arrows (Alt)\n        152:    b'\\x1b[1;3A',\n        160:    b'\\x1b[1;3B',\n        157:    b'\\x1b[1;3C',\n        155:    b'\\x1b[1;3D',\n        # Arrows (Ctrl)\n        141:    b'\\x1b[1;5A',\n        145:    b'\\x1b[1;5B',\n        116:    b'\\x1b[1;5C',\n        115:    b'\\x1b[1;5D',\n        # Ctrl + Tab\n        148:    b'\\x1b[2J',\n        # Cursor (Home, Ins, Del...)\n        71:     b'\\x1b[1~',\n        82:     b'\\x1b[2~',\n        83:     b'\\x1b[3~',\n        79:     b'\\x1b[4~',\n        73:     b'\\x1b[5~',\n        81:     b'\\x1b[6~',\n        # Cursor + Alt\n        151:    b'\\x1b[1;3~',\n        162:    b'\\x1b[2;3~',\n        163:    b'\\x1b[3;3~',\n        159:    b'\\x1b[4;3~',\n        153:    b'\\x1b[5;3~',\n        161:    b'\\x1b[6;3~',\n        # Cursor + Ctrl (xterm)\n        119:    b'\\x1b[1;5H',\n        146:    b'\\x1b[2;5~',\n        147:    b'\\x1b[3;5~',\n        117:    b'\\x1b[1;5F',\n        114:    b'\\x1b[5;5~',\n        118:    b'\\x1b[6;5~',\n        # Function Keys (F1 - F12)\n        59:     b'\\x1b[11~',\n        60:     b'\\x1b[12~',\n        61:     b'\\x1b[13~',\n        62:     b'\\x1b[14~',\n        63:     b'\\x1b[15~',\n        64:     b'\\x1b[17~',\n        65:     b'\\x1b[18~',\n        66:     b'\\x1b[19~',\n        67:     b'\\x1b[20~',\n        68:     b'\\x1b[21~',\n        133:    b'\\x1b[23~',\n        134:    b'\\x1b[24~',\n        # Function Keys + Shift (F11 - F22)\n        84:     b'\\x1b[23;2~',\n        85:     b'\\x1b[24;2~',\n        86:     b'\\x1b[25~',\n        87:     b'\\x1b[26~',\n        88:     b'\\x1b[28~',\n        89:     b'\\x1b[29~',\n        90:     b'\\x1b[31~',\n        91:     b'\\x1b[32~',\n        92:     b'\\x1b[33~',\n        93:     b'\\x1b[34~',\n        135:    b'\\x1b[20;2~',\n        136:    b'\\x1b[21;2~',\n        # Function Keys + Ctrl (xterm)\n        94:     b'\\x1bOP',\n        95:     b'\\x1bOQ',\n        96:     b'\\x1bOR',\n        97:     b'\\x1bOS',\n        98:     b'\\x1b[15;2~',\n        99:     b'\\x1b[17;2~',\n        100:    b'\\x1b[18;2~',\n        101:    b'\\x1b[19;2~',\n        102:    b'\\x1b[20;3~',\n        103:    b'\\x1b[21;3~',\n        137:    b'\\x1b[23;3~',\n        138:    b'\\x1b[24;3~',\n        # Function Keys + Alt (xterm)\n        104:    b'\\x1b[11;5~',\n        105:    b'\\x1b[12;5~',\n        106:    b'\\x1b[13;5~',\n        107:    b'\\x1b[14;5~',\n        108:    b'\\x1b[15;5~',\n        109:    b'\\x1b[17;5~',\n        110:    b'\\x1b[18;5~',\n        111:    b'\\x1b[19;5~',\n        112:    b'\\x1b[20;5~',\n        113:    b'\\x1b[21;5~',\n        139:    b'\\x1b[23;5~',\n        140:    b'\\x1b[24;5~',\n    }\n    \"\"\"\n    Pause/Break, Ctrl+Alt+Del, Ctrl+Alt+arrows not mapable\n    key: ordinal of char from msvcrt.getch()\n    value: bytes string of ANSI escape sequence for linux/xterm\n            numerical used over linux specifics for Home and End\n            VT or CSI escape sequences used when linux has no sequence\n            something unique for keys without an escape function\n            0x1b == Escape key\n    \"\"\"\n\n    IS_MSWIN = platform == 'win32'\n    \"\"\"Whether we run on crap OS.\"\"\"\n\n    def __init__(self):\n        self._termstates = []\n\n    def init(self, fullterm: bool) -> None:\n        \"\"\"Internal terminal initialization function\"\"\"\n        if not self.IS_MSWIN:\n            self._termstates = [(t.fileno(),\n                                tcgetattr(t.fileno()) if t.isatty() else None)\n                                for t in (stdin, stdout, stderr)]\n            tfd, istty = self._termstates[0]\n            if istty:\n                new = tcgetattr(tfd)\n                new[3] = new[3] & ~ICANON & ~ECHO\n                new[6][VMIN] = 1\n                new[6][VTIME] = 0\n                if fullterm:\n                    new[6][VINTR] = 0\n                    new[6][VSUSP] = 0\n                tcsetattr(tfd, TCSANOW, new)\n        else:\n            # Windows black magic\n            # https://stackoverflow.com/questions/12492810\n            call('', shell=True)\n\n    def reset(self) -> None:\n        \"\"\"Reset the terminal to its original state.\"\"\"\n        for tfd, att in self._termstates:\n            # terminal modes have to be restored on exit...\n            if att is not None:\n                tcsetattr(tfd, TCSANOW, att)\n                tcsetattr(tfd, TCSAFLUSH, att)\n\n    @staticmethod\n    def is_term() -> bool:\n        \"\"\"Tells whether the current stdout/stderr stream are connected to a\n        terminal (vs. a regular file or pipe)\"\"\"\n        return stdout.isatty()\n\n    @staticmethod\n    def is_colorterm() -> bool:\n        \"\"\"Tells whether the current terminal (if any) support colors escape\n        sequences\"\"\"\n        terms = ['xterm-color', 'ansi']\n        return stdout.isatty() and environ.get('TERM') in terms\n\n    @classmethod\n    def getkey(cls) -> bytes:\n        \"\"\"Return a key from the current console, in a platform independent\n           way.\n        \"\"\"\n        # there's probably a better way to initialize the module without\n        # relying onto a singleton pattern. To be fixed\n        if cls.IS_MSWIN:\n            # w/ py2exe, it seems the importation fails to define the global\n            # symbol 'msvcrt', to be fixed\n            while True:\n                char = msvcrt.getch()\n                if char == b'\\r':\n                    return b'\\n'\n                return char\n        else:\n            char = os_read(stdin.fileno(), 1)\n            return char\n\n    @classmethod\n    def getch_to_escape(cls, char: bytes) -> bytes:\n        \"\"\"Get Windows escape sequence.\"\"\"\n        if cls.IS_MSWIN:\n            return cls.FNKEYS.get(ord(char), char)\n        return char\n"
  },
  {
    "path": "pyftdi/tests/backend/__init__.py",
    "content": "# Copyright (c) 2020, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n"
  },
  {
    "path": "pyftdi/tests/backend/consts.py",
    "content": "\"\"\"Constant importer from existing modules.\"\"\"\n\n# Copyright (c) 2020-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=missing-docstring\n# pylint: disable=invalid-name\n\nfrom enum import Enum\nfrom importlib import import_module\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.misc import EasyDict\n\n\nclass UsbConstants:\n    \"\"\"Expose useful constants defined in PyUSB and allow reverse search, i.e.\n       retrieve constant literals from integral values.\n    \"\"\"\n\n    DEVICE_REQUESTS = {\n        (True, 0x0): 'get_status',\n        (False, 0x1): 'clear_feature',\n        (False, 0x3): 'set_feature',\n        (False, 0x5): 'set_address',\n        (True, 0x6): 'get_descriptor',\n        (False, 0x7): 'set_descriptor',\n        (True, 0x8): 'get_configuration',\n        (False, 0x9): 'set_configuration',\n    }\n\n    def __init__(self):\n        self._desc_type = self._load_constants('desc_type')\n        self._desc_type_mask = self._mask(self._desc_type)\n        self._ctrl_dir = self._load_constants('ctrl')\n        self._ctrl_dir_mask = self._mask(self._ctrl_dir)\n        self._ctrl_type = self._load_constants('ctrl_type')\n        self._ctrl_type_mask = self._mask(self._ctrl_type)\n        self._ctrl_recipient = self._load_constants('ctrl_recipient')\n        self._ctrl_recipient_mask = self._mask(self._ctrl_recipient)\n        self._endpoint_type = self._load_constants('endpoint_type')\n        self._endpoint_type_mask = self._mask(self._endpoint_type)\n        self._descriptors = EasyDict({v.upper(): k\n                                      for k, v in self._desc_type.items()})\n        self.endpoints = self._load_constants('endpoint', True)\n        self.endpoint_types = self._load_constants('endpoint_type', True)\n        self.speeds = self._load_constants('speed', True)\n\n    @property\n    def descriptors(self):\n        return self._descriptors\n\n    @classmethod\n    def _load_constants(cls, prefix: str, reverse=False):\n        prefix = prefix.upper()\n        if not prefix.endswith('_'):\n            prefix = f'{prefix}_'\n        mod = import_module('usb.util')\n        mapping = EasyDict()\n        plen = len(prefix)\n        for entry in dir(mod):\n            if not entry.startswith(prefix):\n                continue\n            if '_' in entry[plen:]:\n                continue\n            if not reverse:\n                mapping[getattr(mod, entry)] = entry[plen:].lower()\n            else:\n                mapping[entry[plen:].lower()] = getattr(mod, entry)\n        if not mapping:\n            raise ValueError(f\"No USB constant found for {prefix.rstrip('_')}\")\n        return mapping\n\n    @classmethod\n    def _mask(cls, mapping: dict) -> int:\n        mask = 0\n        for val in mapping:\n            mask |= val\n        return mask\n\n    def is_req_out(self, reqtype: int) -> str:\n        return not reqtype & self._ctrl_dir_mask\n\n    def dec_req_ctrl(self, reqtype: int) -> str:\n        return self._ctrl_dir[reqtype & self._ctrl_dir_mask]\n\n    def dec_req_type(self, reqtype: int) -> str:\n        return self._ctrl_type[reqtype & self._ctrl_type_mask]\n\n    def dec_req_rcpt(self, reqtype: int) -> str:\n        return self._ctrl_recipient[reqtype & self._ctrl_recipient_mask]\n\n    def dec_req_name(self, reqtype: int, request: int) -> str:\n        direction = bool(reqtype & self._ctrl_dir_mask)\n        try:\n            return self.DEVICE_REQUESTS[(direction, request)]\n        except KeyError:\n            return f'req x{request:02x}'\n\n    def dec_desc_type(self, desctype: int) -> str:\n        return self._desc_type[desctype & self._desc_type_mask]\n\n\nclass FtdiConstants:\n    \"\"\"Expose useful constants defined in Ftdi and allow reverse search, i.e.\n       retrieve constant literals from integral values.\n    \"\"\"\n\n    def __init__(self):\n        self._dcache = {}\n        self._rcache = {}\n\n    @classmethod\n    def _load_constants(cls, prefix: str, reverse=False):\n        prefix = prefix.upper()\n        if not prefix.endswith('_'):\n            prefix = f'{prefix}_'\n        mapping = EasyDict()\n        plen = len(prefix)\n        for name in dir(Ftdi):\n            if not name.startswith(prefix):\n                continue\n            if not reverse:\n                mapping[getattr(Ftdi, name)] = name[plen:].lower()\n            else:\n                mapping[name[plen:].lower()] = getattr(Ftdi, name)\n        if not mapping:\n            # maybe an enum\n            prefix = prefix.rstrip('_').lower()\n            for name in dir(Ftdi):\n                if not name.lower().startswith(prefix):\n                    continue\n                item = getattr(Ftdi, name)\n                if issubclass(item, Enum):\n                    if not reverse:\n                        mapping = {en.value: en.name.lower() for en in item}\n                    else:\n                        mapping = {en.name.lower(): en.value for en in item}\n        if not mapping:\n            raise ValueError(f\"No FTDI constant found for \"\n                             f\"{prefix.rstrip('_')}\")\n        return mapping\n\n    def get_name(self, prefix: str, value: int) -> str:\n        if prefix not in self._dcache:\n            self._dcache[prefix] = self._load_constants(prefix)\n        try:\n            return self._dcache[prefix][value]\n        except KeyError:\n            return f'x?{value:04x}'\n\n    def get_value(self, prefix: str, name: str) -> str:\n        if prefix not in self._rcache:\n            self._rcache[prefix] = self._load_constants(prefix, True)\n        try:\n            return self._rcache[prefix][name.lower()]\n        except KeyError as exc:\n            raise ValueError(f'Unknown name {prefix}.{name}') from exc\n\n    def dec_req_name(self, request: int) -> str:\n        return self.get_name('sio_req', request)\n\n\nUSBCONST = UsbConstants()\n\"\"\"Unique instance of USB constant container.\"\"\"\n\nFTDICONST = FtdiConstants()\n\"\"\"Unique instances of FTDI constant container.\"\"\"\n"
  },
  {
    "path": "pyftdi/tests/backend/ftdivirt.py",
    "content": "\"\"\"PyUSB virtual FTDI device.\"\"\"\n\n# Copyright (c) 2020-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=missing-docstring\n# pylint: disable=unused-argument\n# pylint: disable=invalid-name\n\nimport os\nfrom array import array\nfrom binascii import hexlify\nfrom collections import deque\nfrom enum import IntEnum, unique\nfrom logging import getLogger\nfrom struct import calcsize as scalc, pack as spack, unpack as sunpack\nfrom threading import Event, Lock, Thread\nfrom time import sleep, time as now\nfrom typing import (TYPE_CHECKING, List, Mapping, NamedTuple, Optional,\n                    Sequence, Tuple)\nfrom pyftdi.eeprom import FtdiEeprom   # only for consts, do not use code\nfrom .consts import FTDICONST, USBCONST\nfrom .mpsse import VirtMpsseEngine, VirtMpsseTracer\nif TYPE_CHECKING:\n    from .backend import VirtDeviceHandle\n\n\nclass Pipe:\n    \"\"\"Wrapper around os.pipe\n    \"\"\"\n\n    PIPE = {'r': 0, 'w': 1}\n\n    def __init__(self):\n        self._pipe = None\n        self._zombie = False\n\n    def close(self):\n        if self._pipe is None:\n            return\n        os.close(self._pipe[0])\n        os.close(self._pipe[1])\n        self._pipe = None\n        self._zombie = True\n\n    def read(self, count: int) -> bytes:\n        return os.read(self.r, count)\n\n    def write(self, buf: bytes) -> None:\n        os.write(self.w, buf)\n\n    @property\n    def ep_out(self) -> 'Pipe':\n        # if pin is output, return RX endpoint (so client can read from)\n        return self.r\n\n    @property\n    def ep_in(self) -> 'Pipe':\n        # if pin in input, return TX endpoint (so client can write to)\n        return self.w\n\n    def __getattr__(self, name):\n        if self._zombie:\n            raise IOError('Closed pipe')\n        try:\n            pos = self.PIPE[name[0]]\n        except KeyError as exc:\n            raise AttributeError(f'No such pipe attribute: {name}') from exc\n        if not self._pipe:\n            # lazy instanciation\n            self._pipe = os.pipe()\n        return self._pipe[pos]\n\n\nclass Fifo:\n    \"\"\"Communication queue.\"\"\"\n\n    def __init__(self, size: int = 0):\n        self.q = deque()\n        self.lock = Lock()\n        self.event = Event()\n        self.size: int = size\n\n\nclass VirtualFtdiPin:\n    \"\"\"Virtual FTDI pin to enable interconnexion/communication\n    \"\"\"\n\n    class Function(IntEnum):\n\n        TRISTATE = 0\n        GPIO = 1\n        CLOCK = 2\n        STREAM = 3\n\n    def __init__(self, port, position):\n        self.log = getLogger(f'pyftdi.vftdi[{port.iface}].{position}')\n        self._port = port\n        self._position = position\n        self._next_pin = None\n        self._function = self.Function.TRISTATE\n        self._pipe = None\n\n    def connect_to(self, pin: 'VirtualFtdiPin') -> None:\n        if self == pin and self._position == pin.position:\n            raise ValueError(f'Cannot connect pin {self._port.iface}:'\n                             f'{self._position} to itself')\n        self._next_pin = pin\n\n    def disconnect(self, pin: 'VirtualFtdiPin') -> None:\n        self._next_pin = None\n\n    def set_function(self,\n                     function: Optional['VirtualFtdiPin.Function'] = None) \\\n            -> None:\n        self._function = function or self.Function.TRISTATE\n        if self._function != self.Function.STREAM:\n            if self._pipe:\n                self._pipe.close()\n                self._pipe = None\n\n    def set(self, source: bool, signal: bool) -> None:\n        self.log.debug('set %d <- %s', int(signal), 'ext' if source else 'int')\n        if self._function == self.Function.TRISTATE:\n            return\n        if self._function == self.Function.GPIO:\n            if self.direction:\n                if self._next_pin:\n                    # when propagate to another pin, always an external source\n                    self._next_pin.set(True, signal)\n            else:\n                self._port.set_io(self, signal)\n            return\n        self.log.error('GPIO update on a featured pin: %s', self._function)\n\n    def read(self, count: int) -> bytes:\n        # from external\n        if self._function != self.Function.STREAM:\n            raise IOError('Pin is not of stream kind')\n        if self._pipe is None:\n            self._pipe = Pipe()\n        return self._pipe.read(count)\n\n    def push_to_pin(self, buf: bytes) -> None:\n        # from internal\n        if self._function != self.Function.STREAM:\n            raise IOError('Pin is not of stream kind')\n        if self.is_connected:\n            self._next_pin.write(buf)\n        if self._pipe is None:\n            self._pipe = Pipe()\n        self._pipe.write(buf)\n\n    def write(self, buf: bytes) -> None:\n        # from external\n        if self._function != self.Function.STREAM:\n            raise IOError('Pin is not of stream kind')\n        if self.is_connected:\n            raise IOError(f'Cannot write to connected pin ({self._position})')\n        self._port.write_from_pin(self._position, buf)\n\n    @property\n    def port(self) -> 'VirtFtdiPort':\n        return self._port\n\n    @property\n    def connection(self) -> Optional['VirtualFtdiPin']:\n        return self._next_pin\n\n    @property\n    def is_connected(self) -> bool:\n        return bool(self._next_pin)\n\n    @property\n    def position(self) -> int:\n        return self._position\n\n    @property\n    def direction(self) -> bool:\n        return bool(self._port.direction & (1 << self._position))\n\n    @property\n    def signal(self) -> Optional[bool]:\n        if self._function == self.Function.TRISTATE:\n            return None\n        if self._function == self.Function.GPIO:\n            return bool(self._port.gpio & (1 << self._position))\n        raise IOError('No signal available from this pin')\n\n\nclass VirtFtdiPort:\n    \"\"\"Virtual FTDI port/interface\n\n       :param iface: the interface number (start from 1)\n    \"\"\"\n\n    POLL_DELAY = 1e-3\n    \"\"\"Delay between processing (generating) a chunk of data.\"\"\"\n    SLEEP_DELAY = 200e-3\n    \"\"\"Timeout when the worker is idle.\"\"\"\n\n    class Fifos(NamedTuple):\n        rx: Fifo  # Host-to-FTDI\n        tx: Fifo  # FTDI-to-host\n\n    @unique\n    class BitMode(IntEnum):\n        \"\"\"Function mode selection.\n\n           It is used so many times hereafter than relying on FTDICONST is\n           not very handy, so redefine it\n        \"\"\"\n\n        RESET = 0x00    # switch off altnerative mode (default to UART)\n        BITBANG = 0x01  # classical asynchronous bitbang mode\n        MPSSE = 0x02    # MPSSE mode, available on 2232x chips\n        SYNCBB = 0x04   # synchronous bitbang mode\n        MCU = 0x08      # MCU Host Bus Emulation mode,\n        OPTO = 0x10     # Fast Opto-Isolated Serial Interface Mode\n        CBUS = 0x20     # Bitbang on CBUS pins of R-type chips\n        SYNCFF = 0x40   # Single Channel Synchronous FIFO mode\n\n    FIFO_SIZES = {\n        0x0200: (128, 128),    # FT232AM:   TX: 128, RX: 128\n        0x0400: (128, 384),    # FT232BM:   TX: 128, RX: 384\n        0x0500: (128, 384),    # FT2232C:   TX: 128, RX: 384\n        0x0600: (256, 128),    # FT232R:    TX: 256, RX: 128\n        0x0700: (4096, 4096),  # FT2232H:   TX: 4KiB, RX: 4KiB\n        0x0800: (2048, 2048),  # FT4232H:   TX: 2KiB, RX: 2KiB\n        0x0900: (1024, 1024),  # FT232H:    TX: 1KiB, RX: 1KiB\n        0x1000: (512, 512),    # FT-X:      TX: 512, RX: 512\n        0x3600: (2048, 2048),  # FT4232HA:  TX: 2KiB, RX: 2KiB\n    }\n    \"\"\"FTDI chip internal FIFO sizes.\n\n       TX: to-host, RX: from-host\n    \"\"\"\n\n    WIDTHS = {\n        0x0200: 8,\n        0x0400: 8,\n        0x0500: 12,\n        0x0600: 8,\n        0x0700: 16,\n        0x0800: 8,\n        0x0900: 16,\n        0x1000: 8,\n        0x3600: 8}\n    \"\"\"Interterface pin count.\"\"\"\n\n    UART_PINS = IntEnum('UartPins', 'TXD RXD RTS CTS DTR DSR DCD RI', start=0)\n    \"\"\"Function of pins in UART mode.\"\"\"\n\n    @unique\n    class Command(IntEnum):\n        \"\"\"Command type for command queue (TX thread).\"\"\"\n\n        TERMINATE = 0x1    # Terminate thread\n        SET_BITMODE = 0x2  # Change the bitmode\n\n    def __init__(self, parent: 'VirtFtdi', iface: int):\n        self.log = getLogger(f'pyftdi.vftdi[{iface}]')\n        self._parent = parent\n        self._iface: int = iface\n        self._bitmode = self.BitMode.RESET\n        self._hispeed: bool = False\n        self._baudrate: int = 0\n        self._bb_clock: int = 0\n        self._mpsse: Optional[VirtMpsseTracer] = None\n        self._direction: int = 0\n        self._gpio: int = 0\n        self._width = self.WIDTHS[parent.version]\n        self._pins: List[VirtualFtdiPin] = []\n        self._fifos: VirtFtdiPort.Fifos = VirtFtdiPort.Fifos(\n            Fifo(self.FIFO_SIZES[self._parent.version][1]),\n            Fifo(self.FIFO_SIZES[self._parent.version][0]))\n        self._cbus_dir: int = 0  # logical (commands)\n        self._cbus: int = 0  # logical (commands)\n        self._cbus_map: Optional[Mapping[int, int]] = None  # logical to phys.\n        self._cbusp_gpio: int = 0  # physical (pins)\n        self._cbusp_force: int = 0  # physical (pins)\n        self._cbusp_active: int = 0  # physical (pins)\n        self._resume: bool = True\n        self._cmd_q = Fifo()\n        self._last_txw_ts = 0\n        for pin in range(self._width):\n            self._pins.append(VirtualFtdiPin(self, pin))\n        self._rx_thread = Thread(\n            target=self._rx_worker,\n            name=f'Ftdi-{parent.bus}:{parent.address}/{iface}-Rx',\n            daemon=True)\n        self._tx_thread = Thread(\n            target=self._tx_worker,\n            name=f'Ftdi-{parent.bus}:{parent.address}/{iface}-Tx',\n            daemon=True)\n        self._rx_thread.start()\n        self._tx_thread.start()\n\n    def close(self, freeze: bool = False) -> None:\n        self.log.debug('> close %d', freeze)\n        if self._tx_thread:\n            with self._cmd_q.lock:\n                self._cmd_q.q.append((self.Command.TERMINATE,))\n                self._cmd_q.event.set()\n        timeout = now()+.5\n        while self._cmd_q.q:\n            sleep(50e-3)\n            if now() >= timeout:\n                self.log.warning('aborting before full completion')\n                break\n        self._resume = False\n        if self._rx_thread:\n            self._rx_thread.join()\n            self._rx_thread = None\n        if self._tx_thread:\n            self._tx_thread.join()\n            self._tx_thread = None\n\n    def terminate(self):\n        self.close()\n\n    def __getitem__(self, index: int) -> VirtualFtdiPin:\n        if not isinstance(index, int):\n            raise TypeError(f\"sequence index must be integer, \"\n                            f\"not '{index.__class__.__name__}'\")\n        try:\n            return self._pins[index]\n        except IndexError:\n            raise IndexError(f'Invalid pin: {index}') from None\n\n    @property\n    def iface(self) -> int:\n        return self._iface\n\n    @property\n    def baudrate(self) -> int:\n        return self._baudrate\n\n    @property\n    def width(self) -> int:\n        return self._width\n\n    @property\n    def direction(self) -> int:\n        return self._direction\n\n    @property\n    def gpio(self) -> int:\n        return self._gpio\n\n    @property\n    def cbus(self) -> Tuple[int, int]:\n        \"\"\"Emulate CBUS output (from FTDI to peripheral).\n\n           :return: a tuple of logical value on pins, active pins.\n                    non-active pins should be considered as High-Z\n        \"\"\"\n        return self._cbus_read()\n\n    @cbus.setter\n    def cbus(self, cbus: int) -> None:\n        \"\"\"Emulate CBUS input (from peripheral to FTDI).\"\"\"\n        self._cbus_write(cbus)\n\n    def write(self, data: array, timeout: int) -> int:\n        rx_fifo = self._fifos.rx\n        with rx_fifo.lock:\n            count = len(rx_fifo.q)\n            rx_fifo.q.append(data)\n            rx_fifo.event.set()\n        if not count:\n            # the FIFO was empty, so wait for the first request to complete\n            # this is a hackish way to ensure a request when the device is\n            # not busy handling his FIFOs responds \"immediately\" to the\n            # first request\n            timeout = now()+2  # note that verbose traces may trigger a timeout\n            while self._resume and rx_fifo.q:\n                sleep(self.POLL_DELAY)\n                if now() >= timeout:\n                    raise RuntimeError('RX queue seems stalled')\n        return len(data)\n\n    def read(self, buff: array, timeout: int) -> int:\n        tx_fifo = self._fifos.tx\n        count = len(buff)\n        if count < 2:\n            self.log.warning('Never handle buffers that cannot fit status')\n            return 0\n        if self._bitmode in (self.BitMode.RESET,\n                             self.BitMode.BITBANG,\n                             self.BitMode.SYNCBB,\n                             self.BitMode.MPSSE):\n            status = self.modem_status\n            buff[0], buff[1] = status[0], status[1]\n            pos = 2\n            with tx_fifo.lock:\n                while tx_fifo.q and pos < count:\n                    buff[pos] = tx_fifo.q.popleft()\n                    pos += 1\n            return pos\n        self.log.warning('Read buffer discarded, mode %s',\n                         self.BitMode(self._bitmode).name)\n        self.log.warning('. bbr (%d)', len(buff))\n        return 0\n\n    def set_io(self, pin: VirtualFtdiPin, signal: bool) -> None:\n        position = pin.position\n        if not 0 <= position < self._width:\n            raise ValueError(f'Invalid pin: {position}')\n        if signal:\n            self._update_gpio(True, self._gpio | (1 << position))\n        else:\n            self._update_gpio(True, self._gpio & ~(1 << position))\n\n    def update_gpio(self, mpsse: VirtMpsseEngine, source: bool,\n                    direction: int, gpio: int) -> None:\n        self._direction = direction\n        self._update_gpio(source, gpio)\n\n    @property\n    def modem_status(self) -> Tuple[int, int]:\n        # For some reason, B0 high nibble matches the LPC214x UART:UxMSR\n        # B0.0  ?\n        # B0.1  ?\n        # B0.2  ?\n        # B0.3  ?\n        # B0.4  Clear to send (CTS)\n        # B0.5  Data set ready (DTS)\n        # B0.6  Ring indicator (RI)\n        # B0.7  Receive line signal / Data carrier detect (RLSD/DCD)\n\n        # For some reason, B1 exactly matches the LPC214x UART:UxLSR\n        # B1.0  Data ready (DR)\n        # B1.1  Overrun error (OE)\n        # B1.2  Parity error (PE)\n        # B1.3  Framing error (FE)\n        # B1.4  Break interrupt (BI)\n        # B1.5  Transmitter holding register (THRE)\n        # B1.6  Transmitter empty (TEMT)\n        # B1.7  Error in RCVR FIFO\n        buf0 = 0x02  # magic constant, no idea for now\n        if self._bitmode == self.BitMode.RESET:\n            cts = 0x01 if self._gpio & 0x08 else 0\n            dsr = 0x02 if self._gpio & 0x20 else 0\n            ri = 0x04 if self._gpio & 0x80 else 0\n            dcd = 0x08 if self._gpio & 0x40 else 0\n            buf0 |= cts | dsr | ri | dcd\n        else:\n            # another magic constant\n            buf0 |= 0x30\n        buf1 = 0\n        rx_fifo = self._fifos.rx\n        with rx_fifo.lock:\n            if not rx_fifo.q:\n                # TX empty -> flag THRE & TEMT (\"TX empty\")\n                buf1 |= 0x40 | 0x20\n        return buf0, buf1\n\n    def control_reset(self, wValue: int, wIndex: int,\n                      data: array) -> None:\n        reset = FTDICONST.get_name('sio_reset', wValue)\n        if reset == 'sio':\n            # the thruth is that FTDI does not document what this command\n            # exactly does...\n            for fifo in self._fifos:\n                with fifo.lock:\n                    fifo.q.clear()\n            self.log.info('> ftdi reset is not fully implemented')\n            self._gpio = 0\n            self._direction = 0\n            self._bitmode = self.BitMode.RESET\n            return\n        if reset == 'purge_tx':\n            fifo = self._fifos.tx\n            self.log.info('> ftdi %s: %d requests', reset, len(fifo.q))\n            with fifo.lock:\n                fifo.q.clear()\n            return\n        if reset == 'purge_rx':\n            fifo = self._fifos.rx\n            self.log.info('> ftdi %s: %d requests', reset, len(fifo.q))\n            with fifo.lock:\n                fifo.q.clear()\n            return\n        self.log.error('Unknown reset kind: %s', reset)\n\n    def control_set_bitmode(self, wValue: int, wIndex: int,\n                            data: array) -> None:\n        direction = wValue & 0xff\n        bitmode = (wValue >> 8) & 0x7F\n        mode = self.BitMode(bitmode).name\n        self.log.info('> ftdi bitmode %s: %s', mode, f'{direction:08b}')\n        self._bitmode = bitmode\n        with self._cmd_q.lock:\n            self._cmd_q.q.append((self.Command.SET_BITMODE, self._bitmode))\n            self._cmd_q.event.set()\n        # be sure to wait for the command to be popped out before resuming\n        loop = 10  # is something goes wrong, do not loop forever\n        while loop:\n            if not self._cmd_q.q:\n                # command queue has been fully processed\n                break\n            if not self._resume:\n                # if the worker threads are ending or have ended, abort\n                self.log.warning('Premature end of worker')\n                return\n            loop -= 1\n            # kepp some time for the commands to be processed\n            sleep(0.05)\n        else:\n            raise RuntimeError(f'Command {self._cmd_q.q[-1][0].name} '\n                               f'not handled')\n        if bitmode == self.BitMode.CBUS:\n            self._cbus_dir = direction >> 4\n            mask = (1 << self._parent.properties.cbuswidth) - 1\n            self._cbus_dir &= mask\n            # clear output pins\n            self._cbus &= ~self._cbus_dir & 0xF\n            # update output pins\n            output = direction & 0xF & self._cbus_dir\n            self._cbus |= output\n            self.log.info('> ftdi cbus dir %s, io %s, mask %s',\n                          f'{self._cbus_dir:04b}',\n                          f'{self._cbus:04b}',\n                          f'{mask:04b}')\n        elif bitmode == self.BitMode.RESET:\n            self._direction = ((1 << VirtFtdiPort.UART_PINS.TXD) |\n                               (1 << VirtFtdiPort.UART_PINS.RTS) |\n                               (1 << VirtFtdiPort.UART_PINS.DTR))\n            self._pins[0].set_function(VirtualFtdiPin.Function.STREAM)\n            self._pins[1].set_function(VirtualFtdiPin.Function.STREAM)\n            for pin in self._pins[2:]:\n                pin.set_function(VirtualFtdiPin.Function.GPIO)\n        else:\n            self._direction = direction\n            for pin in self._pins:\n                pin.set_function(VirtualFtdiPin.Function.GPIO)\n        if bitmode == self.BitMode.MPSSE:\n            for pin in self._pins:\n                pin.set_function(VirtualFtdiPin.Function.GPIO)\n            if not self._mpsse:\n                self._mpsse = VirtMpsseTracer(self, self._parent.version)\n\n    def control_set_latency_timer(self, wValue: int, wIndex: int,\n                                  data: array) -> None:\n        self.log.debug('> ftdi latency timer: %d', wValue)\n\n    def control_set_event_char(self, wValue: int, wIndex: int,\n                               data: array) -> None:\n        char = wValue & 0xFF\n        enable = bool(wValue >> 8)\n        self.log.info('> ftdi %sable event char: 0x%02x',\n                      'en' if enable else 'dis', char)\n\n    def control_set_error_char(self, wValue: int, wIndex: int,\n                               data: array) -> None:\n        char = wValue & 0xFF\n        enable = bool(wValue >> 8)\n        self.log.info('> ftdi %sable error char: 0x%02x',\n                      'en' if enable else 'dis', char)\n\n    def control_read_pins(self, wValue: int, wIndex: int,\n                          data: array) -> bytes:\n        mode = FTDICONST.get_name('bitmode', self._bitmode)\n        self.log.info('> ftdi read_pins %s', mode)\n        if mode == 'cbus':\n            cbus = self._cbus & ~self._cbus_dir & 0xF\n            self.log.info('< cbus 0x%01x: %s', cbus, f'{cbus:04b}')\n            return bytes([cbus])\n        low_gpio = self._gpio & 0xFF\n        self.log.info('< gpio 0x%02x: %s', low_gpio, f'{low_gpio:08b}')\n        return bytes([low_gpio])\n\n    def control_set_baudrate(self, wValue: int, wIndex: int,\n                             data: array) -> None:\n        FRAC_INV_DIV = (0, 4, 2, 1, 3, 5, 6, 7)\n        BAUDRATE_REF_BASE = 3.0E6  # 3 MHz\n        BAUDRATE_REF_HIGH = 12.0E6  # 12 MHz\n        if self._parent.is_hispeed_device or self._parent.is_x_series:\n            wIndex >>= 8\n        divisor = wValue | (wIndex << 16)\n        div = divisor & 0x3FFF\n        hispeed = bool(divisor & 0x20000)\n        if not self._parent.is_hispeed_device and hispeed:\n            raise ValueError('Invalid hispeed mode with non-H series')\n        subdiv_code = (divisor >> 14) & 0x7\n        refclock = BAUDRATE_REF_HIGH if hispeed else BAUDRATE_REF_BASE\n        if div < 2 and subdiv_code:\n            raise ValueError('Invalid sub-divisor with special div')\n        if div == 0:\n            baudrate = 3.0e6\n        elif div == 1:\n            baudrate = 2.0e6\n        else:\n            subdiv = FRAC_INV_DIV[subdiv_code]\n            baudrate = round((refclock * 8) / (div * 8 + subdiv))\n        self._hispeed = hispeed\n        self._baudrate = int(baudrate)\n        multiplier = FTDICONST.get_value('BITBANG_BAUDRATE_RATIO',\n                                         'HIGH' if hispeed else 'BASE')\n        self._bb_clock = self._baudrate * multiplier\n        self.log.info('> ftdi set_baudrate %d bps, HS: %s, bb %d Hz',\n                      baudrate, hispeed, self._bb_clock)\n\n    def control_set_data(self, wValue: int, wIndex: int,\n                         data: array) -> None:\n        self.log.debug('> ftdi set_data (NOT IMPLEMENTED)')\n\n    def control_set_flow_ctrl(self, wValue: int, wIndex: int,\n                              data: array) -> None:\n        self.log.debug('> ftdi set_flow_ctrl (NOT IMPLEMENTED)')\n\n    def control_poll_modem_status(self, wValue: int, wIndex: int,\n                                  data: array) -> None:\n        status = self.modem_status\n        self.log.info('> ftdi poll_modem_status %02x%02x',\n                      status[0], status[1])\n        return status\n\n    def write_from_pin(self, pin: int, buf: bytes) -> None:\n        tx_fifo = self._fifos.tx\n        with tx_fifo.lock:\n            free_count = tx_fifo.size - len(tx_fifo.q)\n            if free_count > 0:\n                tx_fifo.q.extend(buf[:free_count])\n        if free_count < len(buf):\n            self.log.warning('FIFO full, truncated buffer from %d', pin)\n\n    def write_from_mpsse(self, mpsse: VirtMpsseEngine, buf: bytes) -> None:\n        tx_fifo = self._fifos.tx\n        with tx_fifo.lock:\n            free_count = tx_fifo.size - len(tx_fifo.q)\n            if free_count > 0:\n                tx_fifo.q.extend(buf[:free_count])\n        if free_count < len(buf):\n            self.log.warning('FIFO full (%d bytes), truncated buffer',\n                             len(tx_fifo.q))\n        self._mpsse.receive(self._iface, buf)\n\n    def _update_gpio(self, source: bool, gpio: int) -> None:\n        changed = self._gpio ^ gpio\n        self._gpio = gpio\n        if source:\n            # call comes from an external source, no need to propagate more\n            return\n        for pos, pin in enumerate(self._pins):\n            bit = 1 << pos\n            if not bit & self._direction:\n                # do not update input pins\n                continue\n            if bit & changed:\n                pin.set(False, bool(gpio & (1 << pos)))\n\n    def _decode_cbus_x1000(self) -> None:\n        cbus_gpio = 0\n        cbus_force = 0\n        cbus_active = 0\n        for bix in range(4):\n            value = self._parent.eeprom[0x1A + bix]\n            bit = 1 << bix\n            if FtdiEeprom.CBUSX(value).name == 'GPIO':\n                cbus_gpio |= bit\n                cbus_active |= bit\n            elif FtdiEeprom.CBUSX(value).name == 'DRIVE0':\n                cbus_force &= ~bit  # useless, for code symmetry\n                cbus_active |= bit\n            elif FtdiEeprom.CBUSX(value).name == 'DRIVE1':\n                cbus_force |= bit\n                cbus_active |= bit\n        mask = (1 << self._parent.properties.cbuswidth) - 1\n        self._cbusp_gpio = cbus_gpio & mask\n        self._cbusp_force = cbus_force & mask\n        self._cbusp_active = cbus_active & mask\n        self.log.debug('x1000 config gpio %s, force %s, active %s',\n                       f'{self._cbusp_gpio:04b}',\n                       f'{self._cbusp_force:04b}',\n                       f'{self._cbusp_active:04b}')\n\n    def _decode_cbus_x0900(self) -> None:\n        cbus_gpio = 0\n        cbus_force = 0\n        cbus_active = 0\n        for bix in range(5):\n            value = self._parent.eeprom[0x18 + bix]\n            low, high = value & 0x0F, value >> 4\n            bit = 1 << 2*bix\n            if FtdiEeprom.CBUSH(low).name == 'GPIO':\n                cbus_gpio |= bit\n                cbus_active |= bit\n            elif FtdiEeprom.CBUSH(low).name == 'DRIVE0':\n                cbus_force &= ~bit  # useless, for code symmetry\n                cbus_active |= bit\n            elif FtdiEeprom.CBUSH(low).name == 'DRIVE1':\n                cbus_force |= bit\n                cbus_active |= bit\n            bit <<= 1\n            if FtdiEeprom.CBUSH(high).name == 'GPIO':\n                cbus_gpio |= bit\n                cbus_active |= bit\n            elif FtdiEeprom.CBUSH(high).name == 'DRIVE0':\n                cbus_force &= ~bit  # useless, for code symmetry\n                cbus_active |= bit\n            elif FtdiEeprom.CBUSH(high).name == 'DRIVE1':\n                cbus_force |= bit\n                cbus_active |= bit\n        mask = (1 << self._parent.properties.cbuswidth) - 1\n        self._cbusp_gpio = cbus_gpio & mask\n        self._cbusp_force = cbus_force & mask\n        self._cbusp_active = cbus_active & mask\n        self._cbus_map = {0: 5, 1: 6, 2: 8, 3: 9}\n        self.log.debug('x0900 config gpio %s, force %s, active %s',\n                       f'{self._cbusp_gpio:04b}',\n                       f'{self._cbusp_force:04b}',\n                       f'{self._cbusp_active:04b}')\n\n    def _decode_cbus_x0600(self) -> None:\n        cbus_gpio = 0\n        cbus_active = 0\n        bix = 0\n        while True:\n            value = self._parent.eeprom[0x14 + bix]\n            low, high = value & 0x0F, value >> 4\n            bit = 1 << (2*bix)\n            if FtdiEeprom.CBUS(low).name == 'GPIO':\n                cbus_gpio |= bit\n                cbus_active |= bit\n            if bix == 2:\n                break\n            bit <<= 1\n            if FtdiEeprom.CBUS(high).name == 'GPIO':\n                cbus_gpio |= bit\n                cbus_active |= bit\n            bix += 1\n        mask = (1 << self._parent.properties.cbuswidth) - 1\n        self._cbusp_gpio = cbus_gpio & mask\n        self._cbusp_force = 0\n        self._cbusp_active = cbus_active & mask\n        self.log.debug('x0600 config gpio %s, force %s, active %s',\n                       f'{self._cbusp_gpio:04b}',\n                       f'{self._cbusp_force:04b}',\n                       f'{self._cbusp_active:04b}')\n\n    def _cbus_write(self, cbus: int) -> None:\n        # from peripheral to FTDI\n        # mask out CBUS pins which are not configured as GPIOs\n        cbus &= self._cbusp_active\n        cbus &= self._cbusp_gpio\n        self.log.debug('> cbus_write active: %s gpio: %s, force: %s, cbus: %s',\n                       f'{self._cbusp_active:04b}',\n                       f'{self._cbusp_gpio:04b}',\n                       f'{self._cbusp_force:04b}',\n                       f'{cbus:04b}')\n        if self._cbus_map:\n            self.log.info('cbus_write map')\n            # convert physical gpio into logical gpio\n            lgpio = 0\n            for log, phy in self._cbus_map:\n                if cbus & (1 << phy):\n                    lgpio |= 1 << log\n            cbus = lgpio\n        # only consider logical input\n        cbus &= ~self._cbus_dir\n        # combine existing output with new input\n        self._cbus &= ~self._cbus_dir\n        self._cbus |= cbus & ~self._cbus_dir\n\n    def _cbus_read(self) -> Tuple[int, int]:\n        # from FTDI to peripheral\n        cbus = self._cbus\n        self.log.debug('> cbus_read active %s, gpio %s, force %s, cbus %s',\n                       f'{self._cbusp_active:04b}',\n                       f'{self._cbusp_gpio:04b}',\n                       f'{self._cbusp_force:04b}',\n                       f'{cbus:04b}')\n        if self._cbus_map:\n            self.log.info('cbus_read map')\n            # convert logical gpio into physical gpio\n            pgpio = 0\n            for log, phy in self._cbus_map:\n                if cbus & (1 << log):\n                    pgpio |= 1 << phy\n            cbus = pgpio\n        # mask out CBUS pins which are not configured as GPIOs\n        cbus &= self._cbusp_gpio\n        # apply DRIVE1 to gpio\n        cbus |= self._cbusp_force\n        self.log.info('< cbus_read cbus %s, active %s',\n                      f'{cbus:04b}', f'{self._cbusp_active:04b}')\n        return cbus, self._cbusp_active\n\n    def _rx_worker(self):\n        \"\"\"Background handling of data received from host.\"\"\"\n        try:\n            while self._resume:\n                rx_fifo = self._fifos.rx\n                rx_fifo.lock.acquire()\n                if not rx_fifo.q:\n                    rx_fifo.lock.release()\n                    if not rx_fifo.event.wait(self.POLL_DELAY):\n                        continue\n                    rx_fifo.lock.acquire()\n                    rx_fifo.event.clear()\n                    if not rx_fifo.q:\n                        self.log.error('wake up w/o RX data')\n                        rx_fifo.lock.release()\n                        continue\n                rx_fifo.event.clear()\n                data = rx_fifo.q[0]\n                rx_fifo.lock.release()\n                if self._bitmode == self.BitMode.MPSSE:\n                    self._mpsse.send(self._iface, data)\n                elif self._bitmode == self.BitMode.RESET:\n                    self[self.UART_PINS.TXD].push_to_pin(data)\n                elif self._bitmode == self.BitMode.BITBANG:\n                    for byte in data:\n                        # only 8 LSBs are addressable through this command\n                        gpi = self._gpio & ~self._direction & 0xFF\n                        gpo = byte & self._direction & 0xFF\n                        msb = self._gpio & ~0xFF\n                        gpio = gpi | gpo | msb\n                        self._update_gpio(False, gpio)\n                        self.log.debug('. bbw %02x: %s',\n                                       self._gpio, f'{self._gpio:08b}')\n                elif self._bitmode == self.BitMode.SYNCBB:\n                    tx_fifo = self._fifos.tx\n                    lost = 0\n                    for byte in data:\n                        with tx_fifo.lock:\n                            free_count = tx_fifo.size - len(tx_fifo.q)\n                            if free_count > 0:\n                                tx_fifo.q.append(self._gpio & 0xFF)\n                            else:\n                                lost += 1\n                        # only 8 LSBs are addressable through this command\n                        gpi = self._gpio & ~self._direction & 0xFF\n                        gpo = byte & self._direction & 0xFF\n                        msb = self._gpio & ~0xFF\n                        gpio = gpi | gpo | msb\n                        self._update_gpio(False, gpio)\n                        self.log.debug('. bbw %02x: %s',\n                                       self._gpio, f'{self._gpio:08b}')\n                    if lost:\n                        self.log.debug('%d samples lost, TX full', lost)\n                else:\n                    try:\n                        mode = self.BitMode(self._bitmode).name\n                    except ValueError:\n                        mode = 'unknown'\n                    self.log.warning('Write buffer discarded, mode %s', mode)\n                    self.log.warning('. (%d) %s',\n                                     len(data), hexlify(data).decode())\n                with rx_fifo.lock:\n                    rx_fifo.q.popleft()\n            self.log.debug('End of worker %s', self._rx_thread.name)\n        except Exception as exc:\n            self.log.error('Dead of worker %s: %s', self._rx_thread.name, exc)\n            raise\n        finally:\n            # ensure other threads to not run forever if this one is on error\n            if self._resume:\n                self.log.info('RX worker is triggering death')\n            self._resume = False\n\n    def _tx_worker(self):\n        \"\"\"Background handling of data sent to host.\"\"\"\n        bitmode = self.BitMode.RESET\n        _wait_delay = self.SLEEP_DELAY\n        terminated = False\n        try:\n            while self._resume:\n                # in order not to overload CPU with real-time computation,\n                # time is sliced into POLL_DELAY period; every POLL_DELAY\n                # period, this thead generates as many data as the real HW\n                # would have generated during this period (~ epsilon due to\n                # rounding)\n                if not self._cmd_q.q:\n                    if not self._cmd_q.event.wait(_wait_delay):\n                        self._tx_worker_generate(bitmode)\n                        continue\n                with self._cmd_q.lock:\n                    if not self._cmd_q.q:\n                        self.log.error('wake up w/o CMD')\n                        continue\n                    self._cmd_q.event.clear()\n                    command = self._cmd_q.q.popleft()\n                self.log.info('Command %s', self.Command(command[0]).name)\n                if command[0] == self.Command.TERMINATE:\n                    terminated = True\n                    break\n                if command[0] == self.Command.SET_BITMODE:\n                    bitmode = command[1]\n                    self._last_txw_ts = now()\n                    if bitmode in (self.BitMode.BITBANG,\n                                   self.BitMode.SYNCBB):\n                        _wait_delay = self.POLL_DELAY\n                    else:\n                        _wait_delay = self.SLEEP_DELAY\n                    continue\n                self.log.error('Unimplemented support for command %d',\n                               command)\n                continue\n            self.log.debug('End of worker %s', self._tx_thread.name)\n        except Exception as exc:\n            self.log.error('Dead of worker %s: %s', self._tx_thread.name, exc)\n            raise\n        finally:\n            # ensure other threads to not run forever if this one is on error\n            if not terminated and self._resume:\n                self.log.info('TX worker is triggering death')\n            self._resume = False\n\n    def _tx_worker_generate(self, bitmode) -> None:\n        tx_fifo = self._fifos.tx\n        if bitmode == self.BitMode.BITBANG:\n            ts = now()\n            # how much time has elapsed since last background action\n            elapsed = ts-self._last_txw_ts\n            self._last_txw_ts = ts\n            # how many bytes should have been captured since last\n            # action\n            byte_count = round(self._baudrate*elapsed)\n            # fill the TX FIFO with as many bytes, stop if full\n            if byte_count:\n                with tx_fifo.lock:\n                    free_count = tx_fifo.size - len(tx_fifo.q)\n                    push_count = min(free_count, byte_count)\n                    tx_fifo.q.extend([self._gpio] * push_count)\n                self.log.debug('in %.3fms -> %d',\n                               elapsed*1000, push_count)\n\n\nclass VirtFtdi:\n    \"\"\"Virtual FTDI device.\n\n       :param version: FTDI version (device kind)\n       :param eeprom_size: size of external EEPROM size, if any\n    \"\"\"\n\n    class Properties(NamedTuple):\n        \"\"\"Device properties.\"\"\"\n        ifcount: int  # count of interface\n        ifwidth: int  # pin width of an interface\n        cbuswidth: int  # pin width of the control bus\n\n    EXT_EEPROMS: Mapping[str, int] = {\n        '93c46': 128,  # 1024 bits\n        '93c56': 256,  # 2048 bits\n        '93c66': 256,  # 2048 bits  (93C66 seen as 93C56)\n    }\n    \"\"\"External EEPROMs.\"\"\"\n\n    INT_EEPROMS: Mapping[int, int] = {\n        0x0600: 0x80,  # FT232R: 128 bytes, 1024 bits\n        0x1000: 0x400  # FT23*X: 1KiB\n    }\n    \"\"\"Internal EEPROMs.\"\"\"\n\n    PROPERTIES: Mapping[int, Properties] = {\n        0x0200: Properties(1, 8, 0),   # FT232AM\n        0x0400: Properties(1, 8, 0),   # FT232BM\n        0x0500: Properties(1, 8, 0),   # FT2232D\n        0x0600: Properties(1, 8, 5),   # FT232R\n        0x0700: Properties(2, 16, 0),  # FT2232H\n        0x0800: Properties(4, 8, 0),   # FT4232H\n        0x0900: Properties(1, 8, 10),  # FT232H\n        0x1000: Properties(1, 8, 4),   # FT231X\n        0x3600: Properties(4, 8, 0),   # FT4232HA\n    }\n    \"\"\"Width of port/bus (regular, cbus).\"\"\"\n\n    EEPROM_REQ_BASE = FTDICONST.get_value('SIO_REQ', 'EEPROM')\n    \"\"\"Base value for EEPROM request.\"\"\"\n\n    def __init__(self, version: int, bus: int, address: int,\n                 eeprom: Optional[dict] = None):\n        self.log = getLogger('pyftdi.vftdi')\n        self._version = version\n        self._bus: int = bus\n        self._address: int = address\n        self._eeprom: bytearray = self._build_eeprom(version, eeprom)\n        self._ports: List[VirtFtdiPort] = []\n        for iface in range(self.PROPERTIES[self._version].ifcount):\n            self._ports.append(VirtFtdiPort(self, iface+1))\n\n    def close(self, freeze: bool = False) -> None:\n        for port in self._ports:\n            port.close(freeze)\n\n    def terminate(self):\n        self.close()\n\n    @property\n    def version(self) -> int:\n        return self._version\n\n    @property\n    def bus(self) -> int:\n        return self._bus\n\n    @property\n    def address(self) -> int:\n        return self._address\n\n    @property\n    def properties(self) -> 'VirtFtdi.Properties':\n        return self.PROPERTIES[self._version]\n\n    @property\n    def is_mpsse_device(self) -> bool:\n        \"\"\"Tell whether the device has an MPSSE engine\n\n           :return: True if the FTDI device supports MPSSE\n        \"\"\"\n        return self._version in (0x0500, 0x0700, 0x0800, 0x0900)\n\n    @property\n    def is_hispeed_device(self) -> bool:\n        \"\"\"Tell whether the device is a high speed device\n\n           :return: True if the FTDI device is HS\n        \"\"\"\n        return self._version in (0x0700, 0x0800, 0x0900)\n\n    @property\n    def is_x_series(self) -> bool:\n        \"\"\"Tell whether the device is a FT-X device\n\n           :return: True for FT-X device\n        \"\"\"\n        return self._version == 0x1000\n\n    def apply_eeprom_config(self, devdesc: dict,\n                            cfgdescs: Sequence[dict]) -> None:\n        self._load_eeprom(devdesc, cfgdescs)\n\n    def control(self, dev_handle: 'VirtDeviceHandle', bmRequestType: int,\n                bRequest: int, wValue: int, wIndex: int, data: array,\n                timeout: int) -> int:\n        req_ctrl = USBCONST.dec_req_ctrl(bmRequestType)\n        req_type = USBCONST.dec_req_type(bmRequestType)\n        req_rcpt = USBCONST.dec_req_rcpt(bmRequestType)\n        req_desc = ':'.join([req_ctrl, req_type, req_rcpt])\n        req_name = FTDICONST.dec_req_name(bRequest)\n        dstr = (hexlify(data).decode() if USBCONST.is_req_out(bmRequestType)\n                else f'({len(data)})')\n        self.log.debug('> control ftdi hdl %d, %s, %s, '\n                       'val 0x%04x, idx 0x%04x, data %s, to %d',\n                       dev_handle.handle, req_desc, req_name,\n                       wValue, wIndex, dstr, timeout)\n        size = 0\n        try:\n            if bRequest >= self.EEPROM_REQ_BASE:\n                obj = self\n            else:\n                obj = self._ports[0xFF & (wIndex-1)] if wIndex else self\n        except IndexError:\n            raise ValueError(f'Invalid iface: 0x{wIndex:04x} '\n                             f'for {req_name}') from None\n        try:\n            pre = '_' if obj == self else ''\n            handler = getattr(obj, f'{pre}control_{req_name}')\n        except AttributeError:\n            self.log.warning('Unknown request %02x:%04x: %s for %s)',\n                             bRequest, wIndex, req_name,\n                             obj.__class__.__name__)\n            return size\n        buf = handler(wValue, wIndex, data) or b''\n        size = len(buf)\n        data[:size] = array('B', buf)\n        self.log.debug('< (%d) %s', size, hexlify(data[:size]).decode())\n        return size\n\n    def write(self, dev_handle: 'VirtDeviceHandle', ep: int, intf: int,\n              data: array, timeout: int) -> int:\n        return self._ports[intf].write(data, timeout)\n\n    def read(self, dev_handle: 'VirtDeviceHandle', ep: int, intf: int,\n             buff: array, timeout: int) -> int:\n        return self._ports[intf].read(buff, timeout)\n\n    def get_port(self, iface: int):\n        # iface: 1..n\n        return self._ports[iface-1]\n\n    @property\n    def eeprom(self) -> bytes:\n        return bytes(self._eeprom)\n\n    @eeprom.setter\n    def eeprom(self, value: bytes):\n        if len(value) != len(self._eeprom):\n            raise ValueError('EEPROM size mismatch')\n        self._eeprom = bytearray(value)\n\n    @classmethod\n    def _build_eeprom(cls, version, eeprom: Optional[dict]) -> bytearray:\n        size = 0\n        data = b''\n        if eeprom:\n            model = eeprom.get('model', None)\n            if model:\n                if version in cls.INT_EEPROMS:\n                    raise ValueError('No external EEPROM supported on this '\n                                     'device')\n                try:\n                    size = cls.EXT_EEPROMS[model.lower()]\n                except KeyError as exc:\n                    raise ValueError('Unsupported EEPROM model: {model}') \\\n                            from exc\n            data = eeprom.get('data', b'')\n        if version in cls.INT_EEPROMS:\n            int_size = cls.INT_EEPROMS[version]\n            # FT232R, FT230x, FT231x, FT234x\n            if size:\n                if size != int_size:\n                    raise ValueError('Internal EEPROM size cannot be changed')\n            else:\n                size = int_size\n        else:\n            if size and size not in cls.EXT_EEPROMS.values():\n                raise ValueError(f'Invalid EEPROM size: {size}')\n        if data and len(data) > size:\n            raise ValueError('Data cannot fit into EEPROM')\n        buf = bytearray(size)\n        buf[:len(data)] = data\n        return buf\n\n    def _checksum_eeprom(self, data: bytearray) -> int:\n        length = len(data)\n        if length & 0x1:\n            raise ValueError('Length not even')\n        # NOTE: checksum is computed using 16-bit values in little endian\n        # ordering\n        checksum = 0XAAAA\n        mtp = self._version == 0x1000  # FT230X\n        for idx in range(0, length, 2):\n            if mtp and 0x24 <= idx < 0x80:\n                # special MTP user section which is not considered for the CRC\n                continue\n            val = ((data[idx+1] << 8) + data[idx]) & 0xffff\n            checksum = val ^ checksum\n            checksum = ((checksum << 1) & 0xffff) | ((checksum >> 15) & 0xffff)\n        return checksum\n\n    def _load_eeprom(self, devdesc: dict, cfgdescs: Sequence[dict]) -> None:\n        whole = self._version != 0x1000  # FT230X\n        buf = self._eeprom if whole else self._eeprom[:0x100]\n        chksum = self._checksum_eeprom(buf)\n        if chksum:\n            self.log.warning('Invalid EEPROM checksum, ignoring content')\n            return\n        # only apply a subset of what the EEPROM can configure for now\n        devdesc['idVendor'] = sunpack('<H', self._eeprom[2:4])[0]\n        devdesc['idProduct'] = sunpack('<H', self._eeprom[4:6])[0]\n        devdesc['iManufacturer'] = self._decode_eeprom_string(0x0e)\n        devdesc['iProduct'] = self._decode_eeprom_string(0x10)\n        devdesc['iSerialNumber'] = self._decode_eeprom_string(0x12)\n        for desc in cfgdescs:\n            if desc.bConfigurationValue == 0:\n                # only update first configuration\n                desc['bMaxPower'] = self._eeprom[0x09]\n                desc['bmAttributes'] = 0x80 | (self._eeprom[0x08] & 0x0F)\n        cbus_dec = f'_decode_cbus_x{self._version:04x}'\n        try:\n            cbus_func = getattr(self._ports[0], cbus_dec)\n        except AttributeError:\n            self.log.debug('No CBUS support: %s', cbus_dec)\n            return\n        cbus_func()\n\n    def _decode_eeprom_string(self, offset):\n        str_offset, str_size = sunpack('<BB', self._eeprom[offset:offset+2])\n        if str_size:\n            str_size -= scalc('<H')\n            str_offset += scalc('<H')\n            manufacturer = self._eeprom[str_offset:str_offset+str_size]\n            return manufacturer.decode('utf16', errors='ignore')\n        return ''\n\n    def _control_read_eeprom(self, wValue: int, wIndex: int,\n                             data: array) -> Optional[bytes]:\n        self.log.debug('> ftdi read_eeprom @ 0x%04x', wIndex*2)\n        if not self._eeprom:\n            self.log.warning('Missing EEPROM')\n            return None\n        if len(data) != 2:\n            self.log.warning('Unexpected read size: %d', len(data))\n            # resume anyway\n        address = abs(wIndex * 2)\n        if address + 1 > len(self._eeprom):\n            # out of bound\n            self.log.warning('Invalid EEPROM address: 0x%04x', wValue)\n            return None\n        word = bytes(self._eeprom[address: address+2])\n        return word\n\n    def _control_write_eeprom(self, wValue: int, wIndex: int,\n                              data: array) -> None:\n        self.log.info('> ftdi write_eeprom @ 0x%04x', wIndex*2)\n        if not self._eeprom:\n            self.log.warning('Missing EEPROM')\n            return\n        address = abs(wIndex * 2)\n        if address + 1 > len(self._eeprom):\n            # out of bound\n            self.log.warning('Invalid EEPROM address: 0x%04x', wValue)\n            return\n        if self._version == 0x1000:\n            if 0x80 <= address < 0xA0:\n                # those address are R/O on FT230x\n                self.log.warning('Protected EEPROM address: 0x%04x', wValue)\n                return\n        self._eeprom[address: address+2] = spack('<H', wValue)\n\n    def _control_set_baudrate(self, wValue: int, wIndex: int,\n                              data: array) -> None:\n        # set_baudrate is very specific and hack-ish, as the HW is.\n        # the way the interface is encoded is simply ugly (likely a legacy\n        # from single-port device, then FTDI needed to support multi-port\n        # ones and found a hack-ish way to implement it w/o breaking the\n        # existing implementation)\n        if self.is_mpsse_device:\n            iface = wIndex & 0xFF\n            wIndex >>= 16\n        else:\n            iface = 1\n        return self._ports[iface-1].control_set_baudrate(wValue, wIndex, data)\n"
  },
  {
    "path": "pyftdi/tests/backend/loader.py",
    "content": "\"\"\"Virtual USB backend loader.\n\"\"\"\n\n# Copyright (c) 2020-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=missing-docstring\n# pylint: disable=too-few-public-methods\n# pylint: disable=too-many-branches\n# pylint: disable=too-many-statements\n# pylint: disable=too-many-nested-blocks\n# pylint: disable=import-error\n\nfrom binascii import unhexlify\nfrom logging import getLogger\nfrom typing import BinaryIO\nfrom ruamel.yaml import YAML\nfrom pyftdi.misc import to_bool\nfrom pyftdi.usbtools import UsbTools\nfrom .usbvirt import (VirtConfiguration, VirtDevice, VirtInterface,\n                      VirtEndpoint, get_backend)\nfrom .consts import USBCONST\n\n\nclass VirtLoader:\n    \"\"\"Load a virtual USB bus environment from a YaML description stream.\n    \"\"\"\n\n    def __init__(self):\n        self.log = getLogger('pyftdi.virt.backend')\n        self._last_ep_idx = 0\n        self._epprom_backup = b''\n\n    def load(self, yamlfp: BinaryIO) -> None:\n        \"\"\"Load a YaML configuration stream.\n\n           :param yamlfp: YaML stream to be parsed\n        \"\"\"\n        backend = get_backend()\n        with yamlfp:\n            try:\n                for ydef in YAML().load_all(yamlfp):\n                    self._build_root(backend, ydef)\n            except Exception as exc:\n                raise ValueError(f'Invalid configuration: {exc}') from exc\n        self._validate()\n        UsbTools.release_all_devices(VirtDevice)\n        UsbTools.flush_cache()\n\n    def unload(self) -> None:\n        \"\"\"Unload current USB topology, release all allocated devices, and\n           flush UsbTools cache.\n\n           Note that the application should also flush UsbTools cache,\n           or reference to 'disconnected' devices may persist.\n        \"\"\"\n        backend = get_backend()\n        backend.flush_devices()\n        count = UsbTools.release_all_devices(VirtDevice)\n        UsbTools.flush_cache()\n        return count\n\n    def get_virtual_ftdi(self, bus, address):\n        return get_backend().get_virtual_ftdi(bus, address)\n\n    @property\n    def eeprom_backup(self) -> bytes:\n        \"\"\"Return the prefined content of the EEPROM, if any.\n        \"\"\"\n        return self._epprom_backup\n\n    def _validate(self):\n        locations = set()\n        for device in get_backend().devices:\n            # check location on buses\n            location = (device.bus, device.address)\n            if location in locations:\n                raise ValueError('Two devices on same USB location '\n                                 f'{location}')\n            locations.add(location)\n            configs = set()\n            ifaces = set()\n            epaddrs = set()\n            for config in device.configurations:\n                cfgval = config.bConfigurationValue\n                if cfgval in configs:\n                    raise ValueError(f'Config {cfgval} assigned twice')\n                configs.add(cfgval)\n                for iface in config.interfaces:\n                    ifval = iface.bInterfaceNumber\n                    if ifval in ifaces:\n                        raise ValueError(f'Interface {ifval} assigned twice')\n                    ifaces.add(iface)\n                    # check endpoint addresses\n                    for endpoint in iface.endpoints:\n                        epaddr = endpoint.bEndpointAddress\n                        if epaddr in epaddrs:\n                            raise ValueError(f'EP 0x{epaddr:02x} '\n                                             'assigned twice')\n                        epaddrs.add(epaddr)\n\n    def _build_root(self, backend, container):\n        backend.flush_devices()\n        if not isinstance(container, dict):\n            raise ValueError('Top-level not a dict')\n        for ykey, yval in container.items():\n            if ykey != 'devices':\n                continue\n            if not isinstance(yval, list):\n                raise ValueError('Devices not a list')\n            for yitem in yval:\n                if not isinstance(container, dict):\n                    raise ValueError('Device not a dict')\n                self._last_ep_idx = 0\n                device = self._build_device(yitem)\n                device.build()\n                backend.add_device(device)\n\n    def _build_device(self, container):\n        devdesc = None\n        configs = []\n        properties = {}\n        delayed_load = False\n        for ykey, yval in container.items():\n            if ykey == 'descriptor':\n                if not isinstance(yval, dict):\n                    raise ValueError('Device descriptor not a dict')\n                devdesc = self._build_device_descriptor(yval)\n                continue\n            if ykey == 'configurations':\n                if not isinstance(yval, list):\n                    raise ValueError('Configurations not a list')\n                configs = [self._build_configuration(conf) for conf in yval]\n                continue\n            if ykey == 'noaccess':\n                yval = to_bool(yval)\n            if ykey == 'speed' and isinstance(yval, str):\n                try:\n                    yval = USBCONST.speeds[yval]\n                except KeyError as exc:\n                    raise ValueError(f'Invalid device speed {yval}') from exc\n            if ykey == 'eeprom':\n                if not isinstance(yval, dict):\n                    raise ValueError('Invalid EEPROM section')\n                for pkey, pval in yval.items():\n                    if pkey == 'model':\n                        if not isinstance(pval, str):\n                            raise ValueError('Invalid EEPROM model')\n                        continue\n                    if pkey == 'load':\n                        try:\n                            pval = to_bool(pval, permissive=False,\n                                           allow_int=True)\n                            yval[pkey] = pval\n                            delayed_load = pval\n                        except ValueError as exc:\n                            raise ValueError('Invalid EEPROM load option') \\\n                                    from exc\n                        continue\n                    if pkey == 'data':\n                        if isinstance(pval, str):\n                            hexstr = pval.replace(' ', '').replace('\\n', '')\n                            try:\n                                pval = unhexlify(hexstr)\n                                yval[pkey] = pval\n                            except ValueError as exc:\n                                raise ValueError('Invalid EEPROM hex format') \\\n                                        from exc\n                        if not isinstance(pval, bytes):\n                            raise ValueError(f'Invalid EEPROM data '\n                                             f'{type(pval)}')\n                        self._epprom_backup = pval\n                        continue\n                    raise ValueError(f'Unknown EEPROM option {pkey}')\n            properties[ykey] = yval\n        if not devdesc:\n            raise ValueError('Missing device descriptor')\n        if not configs:\n            configs = [self._build_configuration({})]\n        device = VirtDevice(devdesc, **properties)\n        for config in configs:\n            device.add_configuration(config)\n        if delayed_load:\n            device.ftdi.apply_eeprom_config(device.desc,\n                                            [cfg.desc for cfg in configs])\n        return device\n\n    def _build_device_descriptor(self, container) -> dict:\n        kmap = {\n            'usb': 'bcdUSB',\n            'class': 'bDeviceClass',\n            'subclass': 'bDeviceSubClass',\n            'protocol': 'bDeviceProtocol',\n            'maxpacketsize': 'bMaxPacketSize0',\n            'vid': 'idVendor',\n            'pid': 'idProduct',\n            'version': 'bcdDevice',\n            'manufacturer': 'iManufacturer',\n            'product': 'iProduct',\n            'serialnumber': 'iSerialNumber',\n        }\n        kwargs = {}\n        for ckey, cval in container.items():\n            try:\n                dkey = kmap[ckey]\n            except KeyError as exc:\n                raise ValueError(f'Unknown descriptor field {ckey}') from exc\n            kwargs[dkey] = cval\n        return kwargs\n\n    def _build_configuration(self, container):\n        if not isinstance(container, dict):\n            raise ValueError('Invalid configuration entry')\n        cfgdesc = {}\n        interfaces = []\n        for ykey, yval in container.items():\n            if ykey == 'descriptor':\n                if not isinstance(yval, dict):\n                    raise ValueError('Configuration descriptor not a dict')\n                cfgdesc = self._build_config_descriptor(yval)\n                continue\n            if ykey == 'interfaces':\n                if not isinstance(yval, list):\n                    raise ValueError('Interfaces not a list')\n                for conf in yval:\n                    interfaces.extend(self._build_interfaces(conf))\n                continue\n            raise ValueError(f'Unknown config entry {ykey}')\n        if not interfaces:\n            interfaces.extend(self._build_interfaces({}))\n        config = VirtConfiguration(cfgdesc)\n        for iface in interfaces:\n            config.add_interface(iface)\n        return config\n\n    def _build_config_descriptor(self, container) -> dict:\n        kmap = {\n            'attributes': 'bmAttributes',\n            'maxpower': 'bMaxPower',\n            'configuration': 'iConfiguration'\n        }\n        kwargs = {}\n        for ckey, cval in container.items():\n            try:\n                dkey = kmap[ckey]\n            except KeyError as exc:\n                raise ValueError(f'Unknown descriptor field {ckey}') from exc\n            if ckey == 'maxpower':\n                cval //= 2\n            elif ckey == 'attributes':\n                if not isinstance(cval, list):\n                    raise ValueError('Invalid config attributes')\n                aval = 0x80\n                for feature in cval:\n                    if feature == 'selfpowered':\n                        aval |= 1 << 6\n                    if feature == 'wakeup':\n                        aval |= 1 << 5\n                cval = aval\n            elif ckey == 'configuration':\n                pass\n            else:\n                raise ValueError(f'Unknown config descriptor {ckey}')\n            kwargs[dkey] = cval\n        return kwargs\n\n    def _build_interfaces(self, container):\n        if not isinstance(container, dict):\n            raise ValueError('Invalid interface entry')\n        repeat = 1\n        altdef = [{}]\n        for ikey, ival in container.items():\n            if ikey == 'alternatives':\n                if not isinstance(ival, list):\n                    raise ValueError(f'Invalid interface entry {ikey}')\n                if len(ival) > 1:\n                    raise ValueError('Unsupported alternative count')\n                if ival:\n                    altdef = ival\n            elif ikey == 'repeat':\n                if not isinstance(ival, int):\n                    raise ValueError(f'Invalid repeat count {ival}')\n                repeat = ival\n            else:\n                raise ValueError(f'Invalid interface entry {ikey}')\n        ifaces = []\n        while repeat:\n            repeat -= 1\n            ifdesc, endpoints = self._build_alternative(altdef[0])\n            self._last_ep_idx = max(ep.bEndpointAddress & 0x7F\n                                    for ep in endpoints)\n            iface = VirtInterface(ifdesc)\n            for endpoint in endpoints:\n                iface.add_endpoint(endpoint)\n            ifaces.append(iface)\n        return ifaces\n\n    def _build_alternative(self, container):\n        if not isinstance(container, dict):\n            raise ValueError('Invalid alternative entry')\n        ifdesc = {}\n        endpoints = []\n        for ikey, ival in container.items():\n            if ikey == 'descriptor':\n                if not isinstance(ival, dict):\n                    raise ValueError('Interface descriptor not a dict')\n                ifdesc = self._build_interface_descriptor(ival)\n                continue\n            if ikey == 'endpoints':\n                if not isinstance(ival, list):\n                    raise ValueError('Interface encpoints not a list')\n                endpoints = [self._build_endpoint(ep) for ep in ival]\n        if not endpoints:\n            epidx = self._last_ep_idx\n            epidx += 1\n            desc = {'descriptor': {'direction': 'in', 'number': epidx}}\n            ep0 = self._build_endpoint(desc)\n            epidx += 1\n            desc = {'descriptor': {'direction': 'out', 'number': epidx}}\n            ep1 = self._build_endpoint(desc)\n            endpoints = [ep0, ep1]\n        return ifdesc, endpoints\n\n    def _build_interface_descriptor(self, container) -> dict:\n        kmap = {\n            'class': 'bDeviceClass',\n            'subclass': 'bDeviceSubClass',\n            'protocol': 'bDeviceProtocol',\n            'interface': 'iInterface',\n        }\n        kwargs = {}\n        for ckey, cval in container.items():\n            try:\n                dkey = kmap[ckey]\n            except KeyError as exc:\n                raise ValueError(f'Unknown descriptor field {ckey}') from exc\n            kwargs[dkey] = cval\n        return kwargs\n\n    def _build_endpoint(self, container):\n        if not isinstance(container, dict):\n            raise ValueError('Invalid endpoint entry')\n        epdesc = None\n        for ikey, ival in container.items():\n            if ikey == 'descriptor':\n                if not isinstance(ival, dict):\n                    raise ValueError('Interface descriptor not a dict')\n                epdesc = self._build_endpoint_descriptor(ival)\n                continue\n            raise ValueError(f'Unknown config entry {ikey}')\n        if not epdesc:\n            raise ValueError('Missing endpoint descriptor')\n        endpoint = VirtEndpoint(epdesc)\n        return endpoint\n\n    def _build_endpoint_descriptor(self, container) -> dict:\n        kwargs = {}\n        if 'number' not in container:\n            raise ValueError('Missing endpoint number')\n        if 'direction' not in container:\n            raise ValueError('Missing endpoint direction')\n        if 'type' not in container:\n            container = dict(container)\n            container['type'] = 'bulk'\n        for ekey, val in container.items():\n            if ekey == 'maxpacketsize':\n                kwargs['wMaxPacketSize'] = val\n                continue\n            if ekey == 'interval':\n                kwargs['bInterval'] = val\n                continue\n            if ekey == 'direction':\n                try:\n                    value = USBCONST.endpoints[val.lower()]\n                except KeyError as exc:\n                    raise ValueError('Unknown endpoint direction') from exc\n                kwargs.setdefault('bEndpointAddress', 0)\n                kwargs['bEndpointAddress'] |= value\n                continue\n            if ekey == 'number':\n                if not isinstance(val, int) or not 0 < val < 16:\n                    raise ValueError(f'Invalid endpoint number {val}')\n                kwargs.setdefault('bEndpointAddress', 0)\n                kwargs['bEndpointAddress'] |= val\n                continue\n            if ekey == 'type':\n                try:\n                    kwargs['bmAttributes'] = \\\n                        USBCONST.endpoint_types[val.lower()]\n                except KeyError as exc:\n                    raise ValueError('Unknown endpoint type') from exc\n                continue\n            if ekey == 'endpoint':\n                kwargs['iEndpoint'] = val\n                continue\n            raise ValueError(f'Unknown endpoint entry {ekey}')\n        return kwargs\n"
  },
  {
    "path": "pyftdi/tests/backend/mpsse.py",
    "content": "\"\"\"PyUSB virtual FTDI device.\"\"\"\n\n# Copyright (c) 2020, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nfrom collections import deque\nfrom logging import getLogger\nfrom struct import unpack as sunpack\nfrom typing import TYPE_CHECKING, Union\nfrom pyftdi.tracer import FtdiMpsseEngine, FtdiMpsseTracer\nif TYPE_CHECKING:\n    from .ftdivirt import VirtFtdiPort\n\n\nclass VirtMpsseTracer(FtdiMpsseTracer):\n    \"\"\"Reuse MPSSE tracer as a MPSSE command decoder engine.\n    \"\"\"\n\n    def __init__(self, port: 'VirtFtdiPort', version: int):\n        super().__init__(version)\n        self.log = getLogger('pyftdi.virt.mpsse.{port.iface}')\n        self._port = port\n\n    def _get_engine(self, iface: int):\n        iface -= 1\n        try:\n            self._engines[iface]\n        except IndexError as exc:\n            raise ValueError(f'No MPSSE engine available on interface '\n                             f'{iface}') from exc\n        if not self._engines[iface]:\n            self._engines[iface] = VirtMpsseEngine(self, self._port)\n        return self._engines[iface]\n\n\nclass VirtMpsseEngine(FtdiMpsseEngine):\n    \"\"\"Virtual implementation of a MPSSE.\n\n       Far from being complete for now :-)\n    \"\"\"\n\n    def __init__(self, tracer: VirtMpsseTracer, port: 'VirtFtdiPort'):\n        super().__init__(port.iface)\n        self.log = getLogger(f'pyftdi.virt.mpsse.{port.iface}')\n        self._tracer = tracer\n        self._port = port\n        self._width = port.width\n        self._mask = (1 << self._width) - 1\n        self._reply_q = deque()\n\n    def send(self, buf: Union[bytes, bytearray]) -> None:\n        super().send(buf)\n        # cannot post the response before the send() method has completed\n        # see FtdiMpsseEngine.send() for execution steps: expected reply size\n        # is only known (stored) once the command execution has completed\n        self.reply()\n\n    def reply(self) -> None:\n        \"\"\"Post the reply to a command back into the virtual FTDI FIFO.\"\"\"\n        while self._reply_q:\n            self._port.write_from_mpsse(self, self._reply_q.popleft())\n\n    def _cmd_get_bits_low(self):\n        super()._cmd_get_bits_low()\n        byte = self._port.gpio & 0xff\n        buf = bytes([byte])\n        self._reply_q.append(buf)\n        return True\n\n    def _cmd_get_bits_high(self):\n        super()._cmd_get_bits_high()\n        byte = (self._port.gpio >> 8) & 0xff\n        buf = bytes([byte])\n        self._reply_q.append(buf)\n        return True\n\n    def _cmd_set_bits_low(self):\n        buf = self._trace_tx[1:3]\n        if not super()._cmd_set_bits_low():\n            return False\n        port = self._port\n        byte, direction = sunpack('BB', buf)\n        gpi = port.gpio & ~direction & self._mask\n        gpo = byte & direction & self._mask\n        msb = port.gpio & ~0xFF\n        gpio = gpi | gpo | msb\n        port.update_gpio(self, False, direction, gpio)\n        self.log.debug('. bbwl %04x: %s', port.gpio, f'{port.gpio:016b}')\n        return True\n\n    def _cmd_set_bits_high(self):\n        buf = self._trace_tx[1:3]\n        if not super()._cmd_set_bits_high():\n            return False\n        port = self._port\n        byte, direction = sunpack('BB', buf)\n        byte <<= 8\n        direction <<= 8\n        gpi = port.gpio & ~direction & self._mask\n        gpo = byte & direction & self._mask\n        lsb = port.gpio & 0xFF\n        gpio = gpi | gpo | lsb\n        port.update_gpio(self, False, direction, gpio)\n        self.log.debug('. bbwh %04x: %s', port.gpio, f'{port.gpio:016b}')\n        return True\n"
  },
  {
    "path": "pyftdi/tests/backend/usbvirt.py",
    "content": "\"\"\"PyUSB virtual USB backend to intercept all USB requests.\n\n   The role of this module is to enable PyFtdi API testing w/o any FTDI\n   hardware.\n\"\"\"\n\n# Copyright (c) 2020-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=missing-docstring\n# pylint: disable=invalid-name\n# pylint: disable=attribute-defined-outside-init\n\nfrom array import array\nfrom binascii import hexlify\nfrom functools import partial\nfrom importlib import import_module\nfrom logging import getLogger\nfrom struct import calcsize as scalc, pack as spack\nfrom typing import TYPE_CHECKING, List, Mapping, Optional, Tuple\nfrom usb.backend import IBackend\nfrom pyftdi.misc import EasyDict\nfrom .consts import USBCONST\nfrom .ftdivirt import VirtFtdi\nif TYPE_CHECKING:\n    from .loader import VirtLoader\n\n\nclass VirtEndpoint:\n    \"\"\"Fake USB interface endpoint.\n    \"\"\"\n\n    DESCRIPTOR_FORMAT = '<4BHB'\n\n    def __init__(self, defs: dict,\n                 extra: Optional[bytes] = None):\n        class EndpointDescriptor(EasyDict):\n            pass\n        if extra and not isinstance(extra, (bytes, bytearray)):\n            raise ValueError('Invalid extra payload')\n        self.desc = EndpointDescriptor(\n            bLength=scalc(self.DESCRIPTOR_FORMAT),\n            bDescriptorType=USBCONST.descriptors.ENDPOINT,\n            bEndpointAddress=0,\n            bmAttributes=0,\n            wMaxPacketSize=64,\n            bInterval=0,\n            bRefresh=0,\n            bSynchAddress=0,\n            extra_descriptors=extra or b'')\n        self.desc.update(defs)\n\n    def build_strings(self, func):\n        func(self.desc)\n\n    def get_length(self) -> int:\n        return self.desc.bLength\n\n    def __getattr__(self, name):\n        return getattr(self.desc, name)\n\n\nclass VirtInterface:\n    \"\"\"Fake USB configuration interface.\n    \"\"\"\n\n    DESCRIPTOR_FORMAT = '<9B'\n\n    def __init__(self, defs: dict, extra: Optional[bytes] = None):\n        class InterfaceDescriptor(EasyDict):\n            pass\n        if extra and not isinstance(extra, (bytes, bytearray)):\n            raise ValueError('Invalid extra payload')\n        desc = InterfaceDescriptor(\n            bLength=scalc(self.DESCRIPTOR_FORMAT),\n            bDescriptorType=USBCONST.descriptors.INTERFACE,\n            bInterfaceNumber=0,\n            bAlternateSetting=0,\n            bNumEndpoints=0,\n            bInterfaceClass=0xFF,\n            bInterfaceSubClass=0xFF,\n            bInterfaceProtocol=0xFF,\n            iInterface=0,  # String desc index\n            extra_descriptors=extra or b'')\n        desc.update(defs)\n        self.alt = 0\n        self.altsettings: List[Tuple[VirtInterface,\n                                     List[VirtEndpoint]]] = [(desc, [])]\n\n    def add_endpoint(self, endpoint: VirtEndpoint):\n        altsetting = self.altsettings[self.alt]\n        altsetting[1].append(endpoint)\n        altsetting[0].bNumEndpoints = len(altsetting[1])\n\n    def update_number(self, number: int) -> None:\n        self.altsettings[self.alt][0].bInterfaceNumber = number\n\n    @property\n    def endpoints(self):\n        return self.altsettings[self.alt][1]\n\n    def build_strings(self, func):\n        for desc, _ in self.altsettings:\n            func(desc)\n        for _, endpoints in self.altsettings:\n            for endpoint in endpoints:\n                endpoint.build_strings(func)\n\n    def add_bulk_pair(self):\n        endpoints = self.altsettings[self.alt][1]\n        desc = {\n            'bEndpointAddress': len(endpoints)+1 | USBCONST.endpoints['in'],\n            'bmAttributes': USBCONST.endpoint_types['bulk']\n        }\n        ep = VirtEndpoint(desc)\n        self.add_endpoint(ep)\n        desc = {\n            'bEndpointAddress': len(endpoints)+1 | USBCONST.endpoints['out'],\n            'bmAttributes': USBCONST.endpoint_types['bulk']\n        }\n        ep = VirtEndpoint(desc)\n        self.add_endpoint(ep)\n\n    def get_length(self) -> int:\n        length = 0\n        for desc, endpoints in self.altsettings:\n            length += desc.bLength\n            for endpoint in endpoints:\n                length += endpoint.get_length()\n        return length\n\n    @property\n    def num_altsetting(self):\n        return len(self.altsetting)\n\n    def __getitem__(self, item):\n        if isinstance(item, int):\n            return self.altsettings[item]\n        raise IndexError('Invalid alternate setting')\n\n    def __getattr__(self, name):\n        return getattr(self.altsettings[self.alt][0], name)\n\n\nclass VirtConfiguration:\n    \"\"\"Fake USB device configuration.\n    \"\"\"\n\n    DESCRIPTOR_FORMAT = '<2BH5B'\n\n    def __init__(self, defs: dict, extra: Optional[bytes] = None):\n        class ConfigDescriptor(EasyDict):\n            pass\n        if extra and not isinstance(extra, (bytes, bytearray)):\n            raise ValueError('Invalid extra payload')\n        self.desc = ConfigDescriptor(\n            bLength=scalc(self.DESCRIPTOR_FORMAT),\n            bDescriptorType=USBCONST.descriptors.CONFIG,\n            wTotalLength=0,\n            bNumInterfaces=0,\n            bConfigurationValue=0,\n            iConfiguration=0,  # string index\n            bmAttributes=0x80,  # bus-powered\n            bMaxPower=150//2,  # 150 mA\n            extra_descriptors=extra or b'')\n        self.desc.update(defs)\n        self.interfaces: List[VirtInterface] = []\n\n    def add_interface(self, interface: VirtInterface):\n        interface.update_number(len(self.interfaces))\n        self.interfaces.append(interface)\n        self.desc.bNumInterfaces = len(self.interfaces)\n\n    def build_strings(self, func):\n        func(self.desc)\n        for iface in self.interfaces:\n            iface.build_strings(func)\n\n    def update(self):\n        # wTotalLength needs to be updated to the actual length of the\n        # sub-objects\n        self.desc.wTotalLength = self.get_length()\n\n    def get_length(self) -> int:\n        length = self.desc.bLength\n        for iface in self.interfaces:\n            length += iface.get_length()\n        return length\n\n    def __getattr__(self, name):\n        return getattr(self.desc, name)\n\n\nclass VirtDevice:\n    \"\"\"Fake USB device.\n    \"\"\"\n\n    DESCRIPTOR_FORMAT = '<2BH4B3H4B'\n\n    DEFAULT_LANGUAGE = 0x0409  # en_US\n\n    def __init__(self, defs: dict, **kwargs):\n        class DeviceDescriptor(EasyDict):\n            pass\n        self.desc = DeviceDescriptor(\n            bLength=scalc(self.DESCRIPTOR_FORMAT),\n            bDescriptorType=USBCONST.descriptors.DEVICE,\n            bcdUSB=0x200,  # USB 2.0\n            bDeviceClass=0,\n            bDeviceSubClass=0,\n            bDeviceProtocol=0,\n            bMaxPacketSize0=8,\n            idVendor=0,\n            idProduct=0,\n            bcdDevice=0,\n            iManufacturer=0,\n            iProduct=0,\n            iSerialNumber=0,\n            bNumConfigurations=0,  # updated later\n            port_number=None,  # unsupported\n            port_numbers=None,  # unsupported\n            bus=0,\n            address=0,\n            speed=3,\n            eeprom=None)\n        self.desc.update(defs)\n        self._props = set()\n        # pylint: disable=consider-using-dict-items\n        for key in kwargs:\n            # be sure not to allow descriptor override by arbitrary properties\n            if key not in defs:\n                self.desc[key] = kwargs[key]\n                self._props.add(key)\n        self.configurations = []\n        self.strings = ['']  # slot 0 is reserved\n        self._ftdi = VirtFtdi(self.desc.bcdDevice,\n                              self.desc.bus, self.desc.address,\n                              kwargs.get('eeprom', {}))\n\n    def close(self, freeze: bool = False):\n        self._ftdi.close(freeze)\n\n    def terminate(self):\n        self._ftdi.terminate()\n\n    def add_configuration(self, config: VirtConfiguration):\n        config.update()\n        self.configurations.append(config)\n        self.desc.bNumConfigurations = len(self.configurations)\n\n    def build(self):\n        func = partial(VirtDevice._store_strings, self)\n        self.build_strings(func)\n\n    def build_strings(self, func):\n        func(self.desc, self._props)\n        for config in self.configurations:\n            config.build_strings(func)\n\n    @property\n    def ftdi(self) -> VirtFtdi:\n        return self._ftdi\n\n    @staticmethod\n    def _store_strings(obj, desc, ignore=None):\n        for dkey in sorted(desc):\n            if ignore and dkey in ignore:\n                continue\n            if isinstance(desc[dkey], str):\n                stridx = len(obj.strings)\n                obj.strings.append(desc[dkey])\n                desc[dkey] = stridx\n\n    def get_string(self, type_: int, index: int) -> str:\n        if index == 0:\n            if self.desc.get('noaccess', False):\n                # simulate unauthorized access to the USB device\n                return b''\n            # request for list of supported languages\n            # only support one\n            fmt = '<BBH'\n            size = scalc(fmt)\n            buf = spack(fmt, size, type_, self.DEFAULT_LANGUAGE)\n            return buf\n        try:\n            value = self.strings[index]\n        except IndexError:\n            return b''\n        ms_str = value.encode('utf-16-le')\n        fmt = '<BB'\n        size = scalc(fmt) + len(ms_str)\n        buf = bytearray(spack('<BB', size, type_))\n        buf.extend(ms_str)\n        return buf\n\n    def __getattr__(self, name):\n        return getattr(self.desc, name)\n\n\nclass VirtDeviceHandle(EasyDict):\n    \"\"\"Device handle wrapper as expected by PyUSB APIs.\n    \"\"\"\n\n    def __init__(self, dev, handle):\n        super().__init__(handle=handle,\n                         devid=0,\n                         device=dev)\n\n\nclass VirtBackend(IBackend):\n    \"\"\"Fake PyUSB backend.\n\n       Implement a subset of PyUSB IBackend interface so that PyFTDI can\n       execute w/o a real FTDI HW.\n    \"\"\"\n\n    def __init__(self):\n        self.log = getLogger('pyftdi.virt.usb')\n        self._devices: List[VirtDevice] = []\n        self._device_handles: Mapping[int, VirtDeviceHandle] = {}\n        self._device_handle_count: int = 0\n\n    def add_device(self, device: VirtDevice):\n        self._devices.append(device)\n\n    def flush_devices(self):\n        for dev in self._devices:\n            dev.terminate()\n        self._devices.clear()\n\n    @classmethod\n    def create_loader(cls) -> 'VirtLoader':\n        \"\"\"Provide the loader class to configure this virtual backend instance.\n\n           Using this method to retrieve a loader ensure both the virtual\n           backend and the loader have been loaded from the same package.\n\n           :return: the VirtLoader class\n        \"\"\"\n        # this is a bit circumvoluted, but modules cannot cross-reference\n        loader_modname = '.'.join(__name__.split('.')[:-1] + ['loader'])\n        loader_mod = import_module(loader_modname)\n        VirtLoader = getattr(loader_mod, 'VirtLoader')\n        return VirtLoader\n\n    @property\n    def devices(self) -> List[VirtDevice]:\n        return self._devices\n\n    def get_virtual_ftdi(self, bus: int, address: int) -> VirtFtdi:\n        for dev in self._devices:\n            if dev.bus == bus and dev.address == address:\n                return dev.ftdi\n        raise ValueError('No FTDI @ {bus:address}')\n\n    def enumerate_devices(self) -> VirtDevice:\n        yield from self._devices\n\n    def open_device(self, dev: VirtDevice) -> VirtDeviceHandle:\n        self._device_handle_count += 1\n        devhdl = VirtDeviceHandle(dev, self._device_handle_count)\n        self._device_handles[devhdl.handle] = devhdl\n        return devhdl\n\n    def close_device(self, dev_handle: VirtDeviceHandle) -> None:\n        del self._device_handles[dev_handle.handle]\n\n    def claim_interface(self, dev_handle: VirtDeviceHandle, intf: int) \\\n            -> None:\n        self.log.info('> claim interface h:%d: if:%d',\n                      dev_handle.handle, intf)\n\n    def release_interface(self, dev_handle: VirtDeviceHandle, intf: int) \\\n            -> None:\n        self.log.info('> release interface h:%d: if:%d',\n                      dev_handle.handle, intf)\n\n    def get_configuration(self,\n                          dev_handle: VirtDeviceHandle) -> VirtConfiguration:\n        dev = dev_handle.device\n        return dev.configurations[0]\n\n    def set_configuration(self, dev_handle: VirtDeviceHandle,\n                          config_value: int) -> None:\n        # config_value = ConfigDesc.bConfigurationValue\n        pass\n\n    def get_device_descriptor(self, dev: VirtDevice) -> EasyDict:\n        return dev.desc\n\n    def get_configuration_descriptor(self, dev: VirtDevice, config: int) \\\n            -> EasyDict:\n        return dev.configurations[config].desc\n\n    def get_interface_descriptor(self, dev: VirtDevice,\n                                 intf: int, alt: int, config: int) -> EasyDict:\n        cfg = dev.configurations[config]\n        iface = cfg.interfaces[intf]\n        intf_desc = iface[alt][0]\n        return intf_desc\n\n    def get_endpoint_descriptor(self, dev: VirtDevice,\n                                ep: int, intf: int, alt: int, config: int) \\\n            -> EasyDict:\n        cfg = dev.configurations[config]\n        iface = cfg.interfaces[intf]\n        endpoints = iface[alt][1]\n        ep_desc = endpoints[ep].desc\n        return ep_desc\n\n    def ctrl_transfer(self,\n                      dev_handle: VirtDeviceHandle,\n                      bmRequestType: int,\n                      bRequest: int,\n                      wValue: int,\n                      wIndex: int,\n                      data: array,\n                      timeout: int) -> int:\n        req_type = USBCONST.dec_req_type(bmRequestType)\n        if req_type == 'standard':\n            return self._ctrl_standard(dev_handle, bmRequestType, bRequest,\n                                       wValue, wIndex, data, timeout)\n        if req_type == 'vendor':\n            ftdi = dev_handle.device.ftdi\n            return ftdi.control(dev_handle, bmRequestType, bRequest,\n                                wValue, wIndex, data, timeout)\n        self.log.error('Unknown request')\n        return 0\n\n    def bulk_write(self, dev_handle: VirtDeviceHandle, ep: int, intf: int,\n                   data: array, timeout: int) -> int:\n        self.log.debug('> write h:%d ep:%0x02x if:%d, d:%s, to:%d',\n                       dev_handle.handle, ep, intf, hexlify(data).decode(),\n                       timeout)\n        ftdi = dev_handle.device.ftdi\n        return ftdi.write(dev_handle, ep, intf, data, timeout)\n\n    def bulk_read(self, dev_handle: VirtDeviceHandle, ep: int, intf: int,\n                  buff: array, timeout: int) -> int:\n        self.log.debug('> read h:%d ep:0x%02x if:%d, l:%d, to:%d',\n                       dev_handle.handle, ep, intf, len(buff), timeout)\n        ftdi = dev_handle.device.ftdi\n        return ftdi.read(dev_handle, ep, intf, buff, timeout)\n\n    def _ctrl_standard(self,\n                       dev_handle: VirtDeviceHandle,\n                       bmRequestType: int,\n                       bRequest: int,\n                       wValue: int,\n                       wIndex: int,\n                       data: array,\n                       timeout: int) -> int:\n        req_ctrl = USBCONST.dec_req_ctrl(bmRequestType)\n        req_type = USBCONST.dec_req_type(bmRequestType)\n        req_rcpt = USBCONST.dec_req_rcpt(bmRequestType)\n        req_desc = ':'.join([req_ctrl, req_type, req_rcpt])\n        req_name = USBCONST.dec_req_name(bmRequestType, bRequest)\n        dstr = (hexlify(data).decode() if USBCONST.is_req_out(bmRequestType)\n                else f'({len(data)})')\n        self.log.debug('> ctrl_transfer hdl %d, %s, %s, '\n                       'val 0x%04x, idx 0x%04x, data %s, to %d',\n                       dev_handle.handle, req_desc, req_name,\n                       wValue, wIndex, dstr, timeout)\n        size = 0\n        if req_name == 'get_descriptor':\n            desc_idx = wValue & 0xFF\n            desc_type = wValue >> 8\n            self.log.debug('  %s: 0x%02x',\n                           USBCONST.dec_desc_type(desc_type), desc_idx)\n            dev = dev_handle.device\n            buf = dev.get_string(desc_type, desc_idx)\n            size = len(buf)\n            data[:size] = array('B', buf)\n        else:\n            self.log.warning('Unknown request')\n        self.log.debug('< (%d) %s', size, hexlify(data[:size]).decode())\n        return size\n\n\n_VirtBackend = VirtBackend()\n\"\"\"Unique instance of PyUSB virtual backend.\"\"\"\n\n\ndef get_backend(*_):\n    \"\"\"PyUSB API implementation.\"\"\"\n    return _VirtBackend\n"
  },
  {
    "path": "pyftdi/tests/bits.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"BitSequence unit tests.\"\"\"\n\n# Copyright (c) 2010-2024 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2010-2016, Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=broad-except\n# pylint: disable=invalid-name\n# pylint: disable=missing-class-docstring\n# pylint: disable=missing-function-docstring\n\nfrom sys import modules\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom pyftdi.bits import BitSequence, BitZSequence, BitSequenceError\n\n\nclass BitSequenceTestCase(TestCase):\n\n    def setUp(self):\n        self.bs1 = BitSequence(0x01, msb=True, length=8)\n        self.bs2 = BitSequence(0x02, msb=True, length=8)\n        self.bs3 = BitSequence(0x04, msb=True, length=7)\n        self.bs4 = BitSequence(0x04, msb=True, length=11)\n        self.bs5 = BitSequence(299999999999998)\n        self.bs6 = BitSequence(299999999999999)\n        self.bs7 = BitSequence(value='10010101011111')\n        self.bzs1 = BitZSequence(0x01, msb=True, length=8)\n        self.bzs2 = BitZSequence('0Z1')\n        self.bzs3 = BitZSequence('0Z1', length=5)\n        self.bzs4 = BitZSequence('0010ZZ010Z1Z11')\n        self.bzs5 = BitZSequence(value=[True, False, None, False, False, True])\n        self.bzs6 = BitZSequence(value=[True, False, None, False, False, True],\n                                 length=len(self.bzs4))\n\n    def test_bitwise_ops(self):\n        self.assertEqual(int(BitSequence(0x01, length=8) |\n                             BitSequence(0x02, length=8)), 3)\n        self.assertEqual(int(BitSequence(0x07, length=8) &\n                             BitSequence(0x02, length=8)), 2)\n        self.assertEqual(int(BitZSequence(0x01, length=8) |\n                             BitSequence(0x02, length=8)), 3)\n        self.assertEqual(int(BitSequence(0x07, length=8) &\n                             BitZSequence(0x02, length=8)), 2)\n        self.assertRaises(BitSequenceError, BitZSequence.__or__,\n                          self.bzs4, self.bzs5)\n        self.assertRaises(BitSequenceError, BitZSequence.__and__,\n                          self.bzs4, self.bzs5)\n        self.assertEqual(repr(self.bzs6), '00000000100Z01')\n        self.assertEqual(repr(self.bzs6 | self.bzs4), '11Z1Z010ZZ0Z01')\n        self.assertEqual(repr(self.bzs6 & self.bzs4), '00Z0Z000ZZ0Z00')\n        self.assertEqual(repr(self.bzs4 & self.bs7), '11Z1Z010ZZ0000')\n        self.assertEqual(repr(self.bs7 & self.bzs4), '11Z1Z010ZZ0000')\n        self.assertEqual(repr(self.bzs4 | self.bs7), '11Z1Z010ZZ1101')\n        self.assertEqual(repr(self.bs7 | self.bzs4), '11Z1Z010ZZ1101')\n        self.assertEqual(repr(self.bs7.invert()), '00000101010110')\n        self.assertEqual(repr(self.bzs4.invert()), '00Z0Z101ZZ1011')\n        self.assertLess(self.bs5, self.bs6)\n        self.assertLessEqual(self.bs5, self.bs6)\n        self.assertLess(self.bs6, self.bs5)\n        self.assertLessEqual(self.bs6, self.bs5)\n\n    def test_cmp(self):\n        self.assertTrue(self.bs1 == self.bs1)\n        self.assertTrue(self.bs1 != self.bs2)\n        self.assertTrue(self.bs2 != BitSequence(0x02, msb=True, length=4))\n        self.assertTrue(self.bzs2 == self.bzs2)\n        self.assertTrue(self.bzs1 != self.bzs2)\n        self.assertTrue(self.bs1 == self.bzs1)\n        self.assertTrue(self.bzs1 == self.bs1)\n        self.assertTrue(self.bzs3 != self.bzs2)\n        self.assertNotEqual(self.bzs4, self.bzs5)\n        bzs = BitZSequence(self.bs7)\n        self.assertTrue(bzs == self.bs7)\n        bzs |= BitZSequence('00Z0Z000ZZ0Z00')\n        self.assertFalse(bzs == self.bs7)\n        self.assertTrue(bzs.matches(self.bs7))\n\n    def test_representation(self):\n        self.assertEqual(f'{self.bs1} / {self.bs1!r}',\n                         '8: 10000000 / 10000000')\n        self.assertEqual(f'{self.bs2} / {self.bs2!r}',\n                         '8: 01000000 / 01000000')\n        self.assertEqual(f'{self.bs3} / {self.bs3!r}',\n                         '7: 0010000 / 0010000')\n        self.assertEqual(f'{self.bs4} / {self.bs4!r}',\n                         '11: 001 00000000 / 00100000000')\n        self.assertEqual(f'{self.bs5} / {self.bs5!r}',\n                         '49: 1 00010000 11011001 00110001 01101110 10111111 '\n                         '11111110 / 100010000110110010011000101101110101111'\n                         '1111111110')\n        self.assertEqual(f'{self.bs6} / {self.bs6!r}',\n                         '49: 1 00010000 11011001 00110001 01101110 10111111 '\n                         '11111111 / 100010000110110010011000101101110101111'\n                         '1111111111')\n        self.assertEqual(repr(self.bzs4), '11Z1Z010ZZ0100')\n        self.assertEqual(repr(self.bzs5), '100Z01')\n\n    def test_init(self):\n        self.assertEqual(int(BitSequence([0, 0, 1, 0])), 4)\n        self.assertEqual(int(BitSequence((0, 1, 0, 0), msb=True)), 4)\n        self.assertEqual(int(BitSequence(4, length=8)), 4)\n        self.assertEqual(int(BitSequence(int(4), msb=True, length=8)), 32)\n        self.assertEqual(int(BitSequence(\"0010\")), 4)\n        self.assertEqual(int(BitSequence(\"0100\", msb=True)), 4)\n        bs = BitSequence(\"0100\", msb=True)\n        self.assertEqual(bs, BitSequence(bs))\n        bssub = BitSequence(bs[1:3])\n        self.assertEqual(str(bssub), '2: 10')\n        bs[0:3] = '11'\n        self.assertEqual(str(bs), '4: 0011')\n        bzs = BitZSequence(self.bzs4)\n        self.assertEqual(bzs, self.bzs4)\n        bs = BitSequence('11111010101001', msb=True)\n        bs[8:12] = BitSequence(value='0000')\n        self.assertEqual(repr(bs), '11000010101001')\n        try:\n            bs[8:12] = BitZSequence(value='ZZZZ')\n        except BitSequenceError:\n            pass\n        except Exception as exc:\n            self.fail(f'Unexpected exception {exc}')\n        else:\n            self.fail(\"Error was expected\")\n        bs = BitZSequence('1111101010100111Z1Z010ZZ0100', msb=True)\n        bs[8:12] = BitZSequence(value='ZZZZ')\n        self.assertEqual(repr(bs), '1111101010100111ZZZZ10ZZ0100')\n        bs[8:12] = BitSequence(value='0000')\n        self.assertEqual(repr(bs), '1111101010100111000010ZZ0100')\n        n = 548521358\n        bs = BitSequence(bin(n), msb=True)\n        self.assertEqual(int(bs), n)\n        bzs = BitZSequence(bin(n), msb=True)\n        self.assertEqual(str(bzs), '30: 100000 10110001 11000101 10001110')\n        bs = BitSequence(bytes_=[0x44, 0x66, 0xcc], msby=False)\n        self.assertEqual(int(bs), 0x4466cc)\n        bs = BitSequence(bytes_=(0x44, 0x66, 0xcc), msby=True)\n        self.assertEqual(int(bs), 0xcc6644)\n        try:\n            bs = BitSequence(bytes_=[0x44, 0x666, 0xcc], msby=False)\n        except BitSequenceError:\n            pass\n        except Exception as exc:\n            self.fail(f'Unexpected exception {exc}')\n        else:\n            self.fail(\"Error was expected\")\n\n    def test_conversion(self):\n        bs = BitSequence(0xCA, msb=True, length=8)\n        self.assertEqual(f'{bs.tobyte(False):02x}', '53')\n        self.assertEqual(f'{bs.tobyte(True):02x}', 'ca')\n        self.assertEqual(bs, BitSequence(bs.tobyte(True), msb=True, length=8))\n        self.assertRaises(BitSequenceError, BitZSequence.__int__, self.bzs5)\n        self.assertRaises(BitSequenceError, BitZSequence.tobyte, self.bzs5)\n        self.assertRaises(BitSequenceError, BitZSequence.tobytes, self.bzs5)\n        bzs = BitZSequence(0xaa)\n        self.assertEqual(int(bzs), 0xaa)\n\n    def test_misc(self):\n        ba = BitSequence(12, msb=True, length=16)\n        bb = BitSequence(12, msb=True, length=14)\n        bl = [ba, bb]\n        bl.sort(key=int)\n        self.assertEqual(str(bl), \"[00110000000000, 0011000000000000]\")\n        self.assertEqual(str(ba.tobytes()), \"[48, 0]\")\n        self.assertEqual(str(ba.tobytes(True)), \"[0, 12]\")\n        self.assertEqual(str(bb.tobytes(True)), \"[0, 12]\")\n\n        b = BitSequence(length=254)\n        b[0:4] = '1111'\n        self.assertEqual(\n            str(b), '254: 000000 00000000 00000000 00000000 '\n            '00000000 00000000 00000000 00000000 00000000 00000000 00000000 '\n            '00000000 00000000 00000000 00000000 00000000 00000000 00000000 '\n            '00000000 00000000 00000000 00000000 00000000 00000000 00000000 '\n            '00000000 00000000 00000000 00000000 00000000 00000000 00001111')\n        self.assertEqual(\n            str(b.tobytes()),\n            '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\n            '0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15]')\n\n        b = BitSequence(bytes_=[0xa0, '\\x0f', 0x77], msb=False, msby=False)\n        self.assertEqual(str([f'{x:02x}' for x in b.tobytes(False)]),\n                         \"['a0', '0f', '77']\")\n        b = BitSequence(bytes_=[0xa0, '\\x0f', 0x77], msb=True, msby=True)\n        self.assertEqual(str([f'{x:02x}' for x in b.tobytes(True)]),\n                         \"['a0', '0f', '77']\")\n        b = BitSequence(length=7)\n        b[6] = '1'\n        self.assertEqual(str(b), '7: 1000000')\n\n    def test_rotations(self):\n        b = BitSequence('10101110')\n        b.lsr(2)\n        self.assertEqual(str(b), '8: 01011101')\n        b.lsr(10)\n        self.assertEqual(str(b), '8: 01010111')\n        b.rsr(3)\n        self.assertEqual(str(b), '8: 10111010')\n\n    def test_concat(self):\n        self.assertEqual(repr(self.bzs4+self.bzs5), '100Z0111Z1Z010ZZ0100')\n        self.assertEqual(repr(self.bzs4+self.bs7),\n                         '1111101010100111Z1Z010ZZ0100')\n        self.assertEqual(repr(self.bs7+self.bzs4),\n                         '11Z1Z010ZZ010011111010101001')\n\n\ndef suite():\n    suite_ = TestSuite()\n    suite_.addTest(TestLoader().loadTestsFromModule(modules[__name__]))\n    return suite_\n\n\nif __name__ == '__main__':\n    ut_main(defaultTest='suite')\n"
  },
  {
    "path": "pyftdi/tests/cbus.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"CBUS unit tests.\"\"\"\n\n# Copyright (c) 2020-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nimport sys\nfrom doctest import testmod\nfrom os import environ\nfrom sys import modules\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom pyftdi.ftdi import Ftdi, FtdiError\nfrom pyftdi.eeprom import FtdiEeprom\n\n# pylint: disable=empty-docstring\n# pylint: disable=missing-docstring\n\n\nclass CbusOutputGpioTestCase(TestCase):\n    \"\"\"FTDI CBUS GPIO feature test case\"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Default values\"\"\"\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n\n    def test_gpio(self):\n        \"\"\"Simple test to demonstrate ouput bit-banging on CBUS.\n\n           You need a CBUS-capable FTDI (FT232R/FT232H/FT230X/FT231X), whose\n           EEPROM has been configured to support GPIOs on CBUS0 and CBUS3.\n\n           Hard-wiring is required to run this test:\n           * CBUS0 (output) should be connected to CTS (input)\n           * CBUS3 (output) should be connected to DSR (input)\n        \"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url(self.url)\n        # sanity check: device should support CBUS feature\n        self.assertEqual(ftdi.has_cbus, True)\n        eeprom = FtdiEeprom()\n        eeprom.connect(ftdi)\n        # sanity check: device should have been configured for CBUS GPIOs\n        self.assertEqual(eeprom.cbus_mask & 0b1001, 0b1001)\n        # configure CBUS0 and CBUS3 as output\n        ftdi.set_cbus_direction(0b1001, 0b1001)\n        # no input pin available\n        self.assertRaises(FtdiError, ftdi.get_cbus_gpio)\n        for cycle in range(40):\n            value = cycle & 0x3\n            # CBUS0 and CBUS3\n            cbus = ((value & 0x2) << 2) | value & 0x1\n            # for now, need a digital/logic analyzer to validate output\n            ftdi.set_cbus_gpio(cbus)\n            # CBUS0 is connected to CTS, CBUS3 to DSR\n            # need to inverse logical level as RS232 uses negative logic\n            sig = int(not ftdi.get_cts()) | (int(not ftdi.get_dsr()) << 1)\n            self.assertEqual(value, sig)\n\n\nclass CbusInputGpioTestCase(TestCase):\n    \"\"\"FTDI CBUS GPIO feature test case\"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Default values\"\"\"\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n\n    def test_gpio(self):\n        \"\"\"Simple test to demonstrate input bit-banging on CBUS.\n\n           You need a CBUS-capable FTDI (FT232R/FT232H/FT230X/FT231X), whose\n           EEPROM has been configured to support GPIOs on CBUS0 and CBUS3.\n\n           Hard-wiring is required to run this test:\n           * CBUS0 (input) should be connected to RTS (output)\n           * CBUS3 (input) should be connected to DTR (output)\n        \"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url(self.url)\n        # sanity check: device should support CBUS feature\n        self.assertEqual(ftdi.has_cbus, True)\n        eeprom = FtdiEeprom()\n        eeprom.connect(ftdi)\n        # sanity check: device should have been configured for CBUS GPIOs\n        self.assertEqual(eeprom.cbus_mask & 0b1001, 0b1001)\n        # configure CBUS0 and CBUS3 as input\n        ftdi.set_cbus_direction(0b1001, 0b0000)\n        # no output pin available\n        self.assertRaises(FtdiError, ftdi.set_cbus_gpio, 0)\n        for cycle in range(40):\n            rts = bool(cycle & 0x1)\n            dtr = bool(cycle & 0x2)\n            ftdi.set_rts(rts)\n            ftdi.set_dtr(dtr)\n            # need to inverse logical level as RS232 uses negative logic\n            cbus = ~ftdi.get_cbus_gpio()\n            sig = (cbus & 0x1) | ((cbus & 0x8) >> 2)\n            value = cycle & 0x3\n            self.assertEqual(value, sig)\n\n\ndef suite():\n    suite_ = TestSuite()\n    loader = TestLoader()\n    mod = modules[__name__]\n    # peak the test that matches your HW setup, see test doc for details\n    tests = (  # 'CbusOutputGpio',\n             'CbusInputGpio')\n    for testname in tests:\n        testcase = getattr(mod, f'{testname}TestCase')\n        suite_.addTest(loader.loadTestsFromTestCase(testcase))\n    return suite_\n\n\ndef main():\n    testmod(sys.modules[__name__])\n    ut_main(defaultTest='suite')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/tests/eeprom.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"EEPROM unit tests.\"\"\"\n\n# Copyright (c) 2018, Stephen Goadhouse <sgoadhouse@virginia.edu>\n# Copyright (c) 2019-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nimport logging\nfrom doctest import testmod\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom os import environ\nfrom sys import modules, stdout\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.misc import hexdump, to_bool\n\n# pylint: disable=missing-docstring\n\n\nclass EepromTestCase(TestCase):\n    \"\"\"FTDI EEPROM access method test case\"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        \"\"\"Default values\"\"\"\n        cls.eeprom_size = int(environ.get('FTDI_EEPROM_SIZE', '256'))\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi://ftdi:2232h/1')\n\n    def setUp(self):\n        \"\"\"Open a connection to the FTDI, defining which pins are configured as\n           output and input\"\"\"\n        # out_pins value of 0x00 means all inputs\n        out_pins = 0x00\n        try:\n            ftdi = Ftdi()\n            # If you REALLY muck things up, need to use this open_bitbang()\n            # function directly and enter vendor and product ID:\n            # ftdi.open_bitbang(vendor=0x0403, product=0x6011,\n            #                   direction=out_pins)\n            ftdi.open_bitbang_from_url(self.url, direction=out_pins)\n            self.ftdi = ftdi\n        except IOError as exc:\n            raise IOError(f'Unable to open USB port: {exc}') from exc\n\n    def tearDown(self):\n        \"\"\"Close the FTDI connection\"\"\"\n        self.ftdi.close()\n\n    def test_eeprom_read(self):\n        \"\"\"Simple test to demonstrate EEPROM read out.\n        \"\"\"\n        ref_data = self.ftdi.read_eeprom(eeprom_size=self.eeprom_size)\n        print(hexdump(ref_data))\n        # check that the right number of bytes were read\n        self.assertEqual(len(ref_data), self.eeprom_size)\n        # Pull out actual checksum from EEPROM data\n        ck_act = (ref_data[-1] << 8) | ref_data[-2]\n        # compute expected checksum value over the EEPROM contents, except\n        # the EEPROM word\n        ck_expo = self.ftdi.calc_eeprom_checksum(ref_data[:-2])\n        self.assertEqual(ck_act, ck_expo)\n        maxsize = self.eeprom_size\n        # verify access to various data segments\n        segments = ((1, 2), (1, 3), (2, 4), (2, 5), (maxsize-8, 8),\n                    (maxsize-3, 3), (0, maxsize))\n        for start, size in segments:\n            chunk = self.ftdi.read_eeprom(start, size, self.eeprom_size)\n            self.assertEqual(len(chunk), size)\n            self.assertEqual(chunk, ref_data[start:start+size])\n        # verify reject access to various invalid data segments\n        segments = (-1, 2), (0, maxsize+1), (maxsize-6, maxsize+1)\n        for start, size in segments:\n            self.assertRaises(ValueError, self.ftdi.read_eeprom, start, size)\n\n    def test_eeprom_write_reject(self):\n        \"\"\"Simple test to demonstrate rejection of invalid EEPROM write\n           requests.\n        \"\"\"\n        ref_data = self.ftdi.read_eeprom(eeprom_size=self.eeprom_size)\n        # check that the right number of bytes were read\n        self.assertEqual(len(ref_data), self.eeprom_size)\n        # verify reject access to various invalid data segments\n        segments = (-1, 2), (0, 257), (250, 7)\n        for start, size in segments:\n            self.assertRaises(ValueError, self.ftdi.write_eeprom, start,\n                              [0] * size, self.eeprom_size)\n\n    def test_eeprom_write(self):\n        \"\"\"Simple test to demonstrate EEPROM write requests.\n        \"\"\"\n        self.ftdi.write_eeprom(0x80, b'test', eeprom_size=self.eeprom_size)\n\n\ndef suite():\n    suite_ = TestSuite()\n    suite_.addTest(TestLoader().loadTestsFromModule(modules[__name__]))\n    return suite_\n\n\ndef main():\n    if to_bool(environ.get('FTDI_DEBUG', 'off')):\n        FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    level = environ.get('FTDI_LOGLEVEL', 'info').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.set_level(loglevel)\n    testmod(modules[__name__])\n    ut_main(defaultTest='suite')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/tests/eeprom_mock.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) 2019-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"Mock eeprom tests that can be run in CI without a device connected.\"\"\"\n\nimport logging\nfrom os import environ\nfrom sys import modules, stdout\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.eeprom import FtdiEeprom\nfrom pyftdi.misc import to_bool\nfrom pyftdi.ftdi import FtdiError\n\n# pylint: disable=invalid-name\n# pylint: disable=missing-class-docstring\n# pylint: disable=missing-function-docstring\n# pylint: disable=no-member\n\nVirtLoader = None\n\n\nclass FtdiTestCase:\n    \"\"\"Common features for all tests.\n    \"\"\"\n\n    # manufacturer/product/serial number strings to use in tests\n    TEST_MANU_NAME = \"MNAME\"\n    TEST_PROD_NAME = \"PNAME\"\n    TEST_SN = \"SN123\"\n    TEST_CONFIG_FILENAME = ''\n\n    @classmethod\n    def setUpClass(cls):\n        cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off'), permissive=False)\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        cls.loader = None\n\n    @classmethod\n    def tearDownClass(cls):\n        if cls.loader:\n            cls.loader.unload()\n\n    def setUp(self):\n        pass\n\n\nclass EepromMirrorTestCase(FtdiTestCase):\n    \"\"\"Test FTDI EEPROM mirror feature (duplicate eeprom data over 2 eeprom\n       sectors). Generally this is tested with a virtual eeprom (by setting\n       environment variable FTDI_VIRTUAL=on), however you may also test with an\n       actual device at your own risk. Note that none of the tests should\n       commit any of their eeprom changes\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        if VirtLoader:\n            cls.loader = VirtLoader()\n            with open(cls.TEST_CONFIG_FILENAME, 'rb') as yfp:\n                cls.loader.load(yfp)\n        if cls.url == 'ftdi:///1':\n            ftdi = Ftdi()\n            ftdi.open_from_url(cls.url)\n            _ = ftdi.device_port_count\n            ftdi.close()\n\n    def test_mirror_properties(self):\n        \"\"\"Check FtdiEeprom properties are accurate for a device that can\n            mirror\n        \"\"\"\n        # properties should work regardless of if the mirror option is set\n        # or not\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.open(self.url, ignore=True)\n        self.assertTrue(eeprom.has_mirroring)\n        self.assertFalse(eeprom.is_mirroring_enabled)\n        self.assertEqual(eeprom.size // 2, eeprom.mirror_sector)\n        eeprom.close()\n\n        mirrored_eeprom = FtdiEeprom()\n        mirrored_eeprom.enable_mirroring(True)\n        mirrored_eeprom.open(self.url, ignore=True)\n        self.assertTrue(mirrored_eeprom.has_mirroring)\n        self.assertTrue(mirrored_eeprom.is_mirroring_enabled)\n        self.assertEqual(mirrored_eeprom.size // 2,\n                         mirrored_eeprom.mirror_sector)\n        mirrored_eeprom.close()\n\n    def test_mirror_manufacturer(self):\n        \"\"\"Verify manufacturer string is properly duplicated across the 2\n            eeprom sectors\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(True)\n        eeprom.open(self.url, ignore=True)\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        self._check_for_mirrored_eeprom_contents(eeprom)\n\n    def test_mirror_product(self):\n        \"\"\"Verify product string is properly duplicated across the 2 eeprom\n            sectors\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(True)\n        eeprom.open(self.url, ignore=True)\n        eeprom.erase()\n        eeprom.set_product_name(self.TEST_PROD_NAME)\n        self._check_for_mirrored_eeprom_contents(eeprom)\n\n    def test_mirror_serial(self):\n        \"\"\"Verify serial string is properly duplicated across the 2 eeprom\n            sectors\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(True)\n        eeprom.open(self.url, ignore=True)\n        eeprom.erase()\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_mirrored_eeprom_contents(eeprom)\n\n    def test_varstr_combinations(self):\n        \"\"\"Verify various combinations of var strings are properly duplicated\n            across the 2 eeprom sectors\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(True)\n        eeprom.open(self.url, ignore=True)\n\n        # manu + prod str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        eeprom.set_product_name(self.TEST_PROD_NAME)\n        self._check_for_mirrored_eeprom_contents(eeprom)\n\n        # manu + sn str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_mirrored_eeprom_contents(eeprom)\n\n        # prod + sn str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_PROD_NAME)\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_mirrored_eeprom_contents(eeprom)\n\n        # manu + prod + sn str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        eeprom.set_manufacturer_name(self.TEST_PROD_NAME)\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_mirrored_eeprom_contents(eeprom)\n\n    def test_compute_size_detects_mirror(self):\n        \"\"\"Verify the eeproms internal _compute_size method\n            returns the correct bool value when it detects an eeprom mirror\n        \"\"\"\n        # pylint: disable=protected-access\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.open(self.url, ignore=True)\n        _, mirrored = eeprom._compute_size([])\n        self.assertFalse(mirrored)\n        test_buf = bytearray(eeprom.size)\n        sector_mid = eeprom.size // 2\n        for ii in range(sector_mid):\n            test_buf[ii] = ii % 255\n            test_buf[sector_mid+ii] = test_buf[ii]\n        _, mirrored = eeprom._compute_size(bytes(test_buf))\n        self.assertTrue(mirrored)\n\n        # change one byte and confirm failure\n        test_buf[eeprom.size - 2] = test_buf[eeprom.size - 2] - 1\n        _, mirrored = eeprom._compute_size(bytes(test_buf))\n        self.assertFalse(mirrored)\n\n    def _check_for_mirrored_eeprom_contents(self, eeprom: FtdiEeprom):\n        \"\"\"Check that contents of the eeprom is identical over the two\n            sectors\n        \"\"\"\n        sector_size = eeprom.size // 2\n        for ii in range(0, sector_size):\n            self.assertEqual(\n                eeprom.data[ii],\n                eeprom.data[ii + eeprom.mirror_sector],\n                f'Mismatch mirror data @ 0x{ii:02x}: 0x{eeprom.data[ii]:02x} '\n                f'!= 0x{eeprom.data[ii + eeprom.mirror_sector]:02x}')\n\n\nclass NonMirroredEepromTestCase(FtdiTestCase):\n    \"\"\"Test FTDI EEPROM mirror features do not break FTDI devices that do\n       not use mirroring\n    \"\"\"\n    TEST_MANU_NAME = \"MNAME\"\n    TEST_PROD_NAME = \"PNAME\"\n    TEST_SN = \"SN123\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        if VirtLoader:\n            cls.loader = VirtLoader()\n            with open(cls.TEST_CONFIG_FILENAME, 'rb') as yfp:\n                cls.loader.load(yfp)\n        if cls.url == 'ftdi:///1':\n            ftdi = Ftdi()\n            ftdi.open_from_url(cls.url)\n            _ = ftdi.device_port_count\n            ftdi.close()\n\n    def test_mirror_properties(self):\n        \"\"\"Check FtdiEeprom properties are accurate for a device that can not\n           mirror.\n           Only run this test if the device under test is incapable of\n           mirroring\n        \"\"\"\n        if bool(getattr(self, 'DEVICE_CAN_MIRROR', None)):\n            self.skipTest('Mirror properties for devices capable of mirroring '\n                          'are tested in EepromMirrorTestCase')\n        # properties should work regardless of if the mirror option is set\n        # or not\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.open(self.url, ignore=True)\n        self.assertFalse(eeprom.has_mirroring)\n        self.assertFalse(eeprom.is_mirroring_enabled)\n        with self.assertRaises(FtdiError):\n            _ = eeprom.mirror_sector\n        eeprom.close()\n        # even if mirroring is enabled, should still stay false\n        mirrored_eeprom = FtdiEeprom()\n        mirrored_eeprom.enable_mirroring(True)\n        mirrored_eeprom.open(self.url, ignore=True)\n        self.assertFalse(mirrored_eeprom.has_mirroring)\n        self.assertFalse(mirrored_eeprom.is_mirroring_enabled)\n        with self.assertRaises(FtdiError):\n            _ = mirrored_eeprom.mirror_sector\n        mirrored_eeprom.close()\n\n    def test_no_mirror_manufacturer(self):\n        \"\"\"Verify manufacturer string is NOT duplicated/mirrored\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(False)\n        eeprom.open(self.url, ignore=True)\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        self._check_for_non_mirrored_eeprom_contents(eeprom)\n\n    def test_no_mirror_product(self):\n        \"\"\"Verify product string is NOT duplicated/mirrored\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(False)\n        eeprom.open(self.url, ignore=True)\n        eeprom.erase()\n        eeprom.set_product_name(self.TEST_PROD_NAME)\n        self._check_for_non_mirrored_eeprom_contents(eeprom)\n\n    def test_mirror_serial(self):\n        \"\"\"Verify serial string is NOT duplicated/mirrored\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(False)\n        eeprom.open(self.url, ignore=True)\n        eeprom.erase()\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_non_mirrored_eeprom_contents(eeprom)\n\n    def test_varstr_combinations(self):\n        \"\"\"Verify various combinations of var strings are NOT\n        duplicated/mirrored\n        \"\"\"\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.enable_mirroring(False)\n        eeprom.open(self.url, ignore=True)\n\n        # manu + prod str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        eeprom.set_product_name(self.TEST_PROD_NAME)\n        self._check_for_non_mirrored_eeprom_contents(eeprom)\n\n        # manu + sn str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_non_mirrored_eeprom_contents(eeprom)\n\n        # prod + sn str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_PROD_NAME)\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_non_mirrored_eeprom_contents(eeprom)\n\n        # manu + prod + sn str\n        eeprom.erase()\n        eeprom.set_manufacturer_name(self.TEST_MANU_NAME)\n        eeprom.set_manufacturer_name(self.TEST_PROD_NAME)\n        eeprom.set_serial_number(self.TEST_SN)\n        self._check_for_non_mirrored_eeprom_contents(eeprom)\n\n    def test_compute_size_does_not_mirror(self):\n        \"\"\"Verify the eeproms internal _compute_size method returns the correct\n           bool value when it detects no mirroring.\n        \"\"\"\n        # pylint: disable=protected-access\n        if self.DEVICE_CAN_MIRROR:\n            self.skipTest('Mirror properties for devices capable of mirroring '\n                          'are tested in EepromMirrorTestCase')\n        eeprom = FtdiEeprom()\n        eeprom.set_test_mode(True)\n        eeprom.open(self.url, ignore=True)\n        _, mirrored = eeprom._compute_size([])\n        self.assertFalse(mirrored)\n        eeprom.close()\n\n        eeprom = FtdiEeprom()\n        eeprom.open(self.url, ignore=False)\n        _, mirrored = eeprom._compute_size([])\n        self.assertFalse(mirrored)\n        eeprom.close()\n\n    def _check_for_non_mirrored_eeprom_contents(self, eeprom: FtdiEeprom):\n        \"\"\"Check that contents of the eeprom is not mirrored\n        \"\"\"\n        mirror_sector_start = eeprom.size // 2\n        # split eeprom into 2 sectors as would be done if mirroring was enabled\n        # and verify the device is not mirrored\n        normal_mirror_s1 = eeprom.data[:mirror_sector_start]\n        normal_mirror_s2 = eeprom.data[mirror_sector_start:]\n        self.assertNotEqual(normal_mirror_s1, normal_mirror_s2)\n\n\nclass EepromMirrorFt232hTestCase(EepromMirrorTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft232h.yaml'\n\n\nclass EepromMirrorFt2232hTestCase(EepromMirrorTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft2232h.yaml'\n\n\nclass EepromMirrorFt4232hTestCase(EepromMirrorTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft4232h.yaml'\n\n\nclass EepromMirrorFt232rTestCase(NonMirroredEepromTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft232r.yaml'\n    DEVICE_CAN_MIRROR = False\n\n\nclass EepromMirrorFt230xTestCase(NonMirroredEepromTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft230x.yaml'\n    DEVICE_CAN_MIRROR = False\n\n\nclass EepromNonMirroredFt232hTestCase(NonMirroredEepromTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft232h.yaml'\n    DEVICE_CAN_MIRROR = True\n\n\nclass EepromNonMirroredFt2232hTestCase(NonMirroredEepromTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft2232h.yaml'\n    DEVICE_CAN_MIRROR = True\n\n\nclass EepromNonMirroredFt4232hTestCase(NonMirroredEepromTestCase, TestCase):\n    TEST_CONFIG_FILENAME = 'pyftdi/tests/resources/ft4232h.yaml'\n    DEVICE_CAN_MIRROR = True\n\n\ndef suite():\n    suite_ = TestSuite()\n    loader = TestLoader()\n    mod = modules[__name__]\n    tests = []\n    # Test devices that support the mirroring capability\n    tests.extend(('EepromMirrorFt232h',\n                  'EepromMirrorFt2232h',\n                  'EepromMirrorFt4232h'))\n    # Test devices that do not support the mirror capability\n    tests.extend(('EepromMirrorFt232r',\n                  'EepromMirrorFt230x'))\n    # test devices that support the mirroring capability, but have it disabled\n    tests.extend(('EepromNonMirroredFt232h',\n                  'EepromNonMirroredFt2232h',\n                  'EepromNonMirroredFt4232h'))\n    for testname in tests:\n        testcase = getattr(mod, f'{testname}TestCase')\n        suite_.addTest(loader.loadTestsFromTestCase(testcase))\n    return suite_\n\n\ndef virtualize():\n    if not to_bool(environ.get('FTDI_VIRTUAL', 'off')):\n        return\n    # pylint: disable=import-outside-toplevel\n    from pyftdi.usbtools import UsbTools\n    # Force PyUSB to use PyFtdi test framework for USB backends\n    UsbTools.BACKENDS = ('backend.usbvirt', )\n    # Ensure the virtual backend can be found and is loaded\n    backend = UsbTools.find_backend()\n    try:\n        # obtain the loader class associated with the virtual backend\n        # pylint: disable=global-statement\n        global VirtLoader\n        VirtLoader = backend.create_loader()\n    except AttributeError as exc:\n        raise AssertionError('Cannot load virtual USB backend') from exc\n\n\ndef setup_module():\n    # pylint: disable=import-outside-toplevel\n    from doctest import testmod\n    testmod(modules[__name__])\n    debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n    if debug:\n        formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)-7s'\n                                      ' %(name)-20s [%(lineno)4d] %(message)s',\n                                      '%H:%M:%S')\n    else:\n        formatter = logging.Formatter('%(message)s')\n    level = environ.get('FTDI_LOGLEVEL', 'warning').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    FtdiLogger.set_level(loglevel)\n    FtdiLogger.set_formatter(formatter)\n    virtualize()\n\n\ndef main():\n    setup_module()\n    try:\n        ut_main(defaultTest='suite')\n    except KeyboardInterrupt:\n        pass\n\n\nif __name__ == '__main__':\n    # Useful environment variables:\n    #  FTDI_DEVICE: a specific FTDI URL, default to ftdi:///1\n    #  FTDI_LOGLEVEL: a Logger debug level, to define log verbosity\n    #  FTDI_DEBUG: to enable/disable debug mode\n    #  FTDI_VIRTUAL: to use a virtual device rather than a physical device\n    main()\n"
  },
  {
    "path": "pyftdi/tests/ftdi.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"FTDI detection and connection unit tests.\"\"\"\n\n# Copyright (c) 2010-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=missing-docstring\n\nimport logging\nfrom doctest import testmod\nfrom os import environ\nfrom sys import modules, stdout\nfrom time import sleep, time as now\nfrom unittest import TestCase, TestLoader, TestSuite, SkipTest, main as ut_main\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi, FtdiError\nfrom pyftdi.usbtools import UsbTools, UsbToolsError\n\n\nclass FtdiTestCase(TestCase):\n    \"\"\"FTDI driver test case\"\"\"\n\n    def test_multiple_interface(self):\n        # the following calls used to create issues (several interfaces from\n        # the same device). The test expects an FTDI 2232H here\n        ftdi1 = Ftdi()\n        ftdi1.open(vendor=0x403, product=0x6010, interface=1)\n        ftdi2 = Ftdi()\n        ftdi2.open(vendor=0x403, product=0x6010, interface=2)\n        for _ in range(5):\n            print(\"If#1: \", hex(ftdi1.poll_modem_status()))\n            print(\"If#2: \", ftdi2.modem_status())\n            sleep(0.500)\n        ftdi1.close()\n        ftdi2.close()\n\n\nclass HotplugTestCase(TestCase):\n\n    def test_hotplug_discovery(self):\n        \"\"\"Demonstrate how to connect to an hotplugged FTDI device, i.e.\n           an FTDI device that is connected after the initial attempt to\n           enumerate it on the USB bus.\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        ftdi = Ftdi()\n        timeout = now() + 5.0  # sanity check: bail out after 10 seconds\n        while now() < timeout:\n            try:\n                ftdi.open_from_url(url)\n                break\n            except UsbToolsError:\n                UsbTools.flush_cache()\n                sleep(0.05)\n                continue\n        self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI')\n        print('Connected to FTDI', url)\n\n\nclass ResetTestCase(TestCase):\n\n    def test_simple_reset(self):\n        \"\"\"Demonstrate how to connect to an hotplugged FTDI device, i.e.\n           an FTDI device that is connected after the initial attempt to\n           enumerate it on the USB bus.\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        ftdi = Ftdi()\n        ftdi.open_from_url(url)\n        self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI')\n        ftdi.close()\n        self.assertFalse(ftdi.is_connected, 'Unable to close connection')\n        ftdi.open_from_url(url)\n        self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI')\n        ftdi.reset(False)\n\n    def test_dual_if_reset(self):\n        \"\"\"Demonstrate how to connect to an hotplugged FTDI device, i.e.\n           an FTDI device that is connected after the initial attempt to\n           enumerate it on the USB bus.\"\"\"\n        url1 = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        ftdi1 = Ftdi()\n        ftdi1.open_from_url(url1)\n        count = ftdi1.device_port_count\n        if count < 2:\n            ftdi1.close()\n            raise SkipTest('FTDI device is not a multi-port device')\n        next_port = (int(url1[-1]) % count) + 1\n        url2 = f'ftdi:///{next_port}'\n        ftdi2 = Ftdi()\n        self.assertTrue(ftdi1.is_connected, 'Unable to connect to FTDI')\n        ftdi2.open_from_url(url2)\n        # use latenty setting to set/test configuration is preserved\n        ftdi2.set_latency_timer(128)\n        # should be the same value\n        self.assertEqual(ftdi2.get_latency_timer(), 128)\n        self.assertTrue(ftdi2.is_connected, 'Unable to connect to FTDI')\n        ftdi1.close()\n        self.assertFalse(ftdi1.is_connected, 'Unable to close connection')\n        # closing first connection should not alter second interface\n        self.assertEqual(ftdi2.get_latency_timer(), 128)\n        ftdi1.open_from_url(url1)\n        self.assertTrue(ftdi1.is_connected, 'Unable to connect to FTDI')\n        # a FTDI reset should not alter settings...\n        ftdi1.reset(False)\n        self.assertEqual(ftdi2.get_latency_timer(), 128)\n        # ... however performing a USB reset through any interface should alter\n        # any previous settings made to all interfaces\n        ftdi1.reset(True)\n        self.assertNotEqual(ftdi2.get_latency_timer(), 128)\n\n\nclass DisconnectTestCase(TestCase):\n    \"\"\"This test requires user interaction to unplug/plug back the device.\n    \"\"\"\n\n    def test_close_on_disconnect(self):\n        \"\"\"Validate close after disconnect.\"\"\"\n        log = logging.getLogger('pyftdi.tests.ftdi')\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        ftdi = Ftdi()\n        ftdi.open_from_url(url)\n        self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI')\n        print('Please disconnect FTDI device')\n        while ftdi.is_connected:\n            try:\n                ftdi.poll_modem_status()\n            except FtdiError:\n                break\n            sleep(0.1)\n        ftdi.close()\n        print('Please reconnect FTDI device')\n        while True:\n            UsbTools.flush_cache()\n            try:\n                ftdi.open_from_url(url)\n            except (FtdiError, UsbToolsError):\n                log.debug('FTDI device not detected')\n                sleep(0.1)\n            except ValueError:\n                log.warning('FTDI device not initialized')\n                ftdi.close()\n                sleep(0.1)\n            else:\n                log.info('FTDI device detected')\n                break\n        ftdi.poll_modem_status()\n        ftdi.close()\n\n\ndef suite():\n    suite_ = TestSuite()\n    loader = TestLoader()\n    mod = modules[__name__]\n    #  tests = 'Ftdi Hotplug Reset Disconnect'\n    tests = 'Reset Disconnect'\n    for testname in tests.split():\n        testcase = getattr(mod, f'{testname}TestCase')\n        suite_.addTest(loader.loadTestsFromTestCase(testcase))\n    return suite_\n\n\nif __name__ == '__main__':\n    testmod(modules[__name__])\n    FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    level = environ.get('FTDI_LOGLEVEL', 'info').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.set_level(loglevel)\n    ut_main(defaultTest='suite')\n"
  },
  {
    "path": "pyftdi/tests/gpio.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright (c) 2016-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=empty-docstring\n# pylint: disable=global-statement\n# pylint: disable=invalid-name\n# pylint: disable=missing-docstring\n\nimport logging\nfrom collections import deque\nfrom os import environ\nfrom sys import modules, stdout\nfrom time import sleep\nfrom unittest import TestCase, TestLoader, TestSuite, SkipTest, main as ut_main\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.gpio import (GpioAsyncController,\n                         GpioSyncController,\n                         GpioMpsseController)\nfrom pyftdi.misc import to_bool\n\n# When USB virtualization is enabled, this loader is instanciated\nVirtLoader = None\n\n\nclass FtdiTestCase(TestCase):\n    \"\"\"Common features for all tests.\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off'), permissive=False)\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        cls.loader = None\n\n    @classmethod\n    def tearDownClass(cls):\n        if cls.loader:\n            cls.loader.unload()\n\n    def setUp(self):\n        if self.debug:\n            print('.'.join(self.id().split('.')[-2:]))\n\n\nclass GpioAsyncTestCase(FtdiTestCase):\n    \"\"\"FTDI Asynchronous GPIO driver test case.\n\n       Please ensure that the HW you connect to the FTDI port A does match\n       the encoded configuration. Check your HW setup before running this test\n       as it might damage your HW. You've been warned.\n\n       Low nibble is used as input, high nibble is used as output. They should\n       be interconnected as follow:\n\n       * b0 should be connected to b4\n       * b1 should be connected to b5\n       * b2 should be connected to b6\n       * b3 should be connected to b7\n\n       Note that this test cannot work with LC231X board, as FTDI is stupid\n       enough to add ubidirectionnal output buffer on DTR and RTS, so both\n       nibbles have at lest on output pin...\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        if VirtLoader:\n            cls.loader = VirtLoader()\n            with open('pyftdi/tests/resources/ft232r.yaml', 'rb') as yfp:\n                cls.loader.load(yfp)\n            vftdi = cls.loader.get_virtual_ftdi(1, 1)\n            vport = vftdi.get_port(1)\n            # create virtual connections as real HW\n            in_pins = [vport[pos] for pos in range(4)]\n            out_pins = [vport[pos] for pos in range(4, 8)]\n            for in_pin, out_pin in zip(in_pins, out_pins):\n                out_pin.connect_to(in_pin)\n        if cls.url == 'ftdi:///1':\n            # assumes that if not specific device is used, and a multiport\n            # device is connected, there is no loopback wires between pins of\n            # the same port. This hack allows to run the same test with a\n            # FT232H, then a FT2232H for ex, to that with two test sessions\n            # the whole test set is run. If a specific device is selected\n            # assume the HW always match the expected configuration.\n            ftdi = Ftdi()\n            ftdi.open_from_url(cls.url)\n            count = ftdi.device_port_count\n            ftdi.close()\n            cls.skip_loopback = count > 1\n        else:\n            cls.skip_loopback = False\n\n    def test_gpio_freeze(self):\n        \"\"\"Simple test to demonstrate freeze on close.\n\n           For now, it requires a logic analyzer to verify the output,\n           this is not automatically validated by SW\n        \"\"\"\n        direction = 0xFF & ~((1 << 4) - 1)  # 4 Out, 4 In\n        gpio = GpioAsyncController()\n        gpio.configure(self.url, direction=direction, frequency=1e3,\n                       initial=0x0)\n        port = gpio.get_gpio()\n        # emit a sequence as a visual marker on b3,b2,b1,b0\n        port.write([x << 4 for x in range(16)])\n        sleep(0.01)\n        # write 0b0110 to the port\n        port.write(0x6 << 4)\n        sleep(0.001)\n        # close w/o freeze: all the outputs should be reset (usually 0b1111)\n        # it might need pull up (or pull down) to observe the change as\n        # output are not high-Z.\n        gpio.close()\n        sleep(0.01)\n        gpio.configure(self.url, direction=direction, frequency=1e3,\n                       initial=0x0)\n        port = gpio.get_gpio()\n        # emit a sequence as a visual marker with on b3 and b1\n        port.write([(x << 4) & 0x90 for x in range(16)])\n        sleep(0.01)\n        # write 0b0110 to the port\n        port.write(0x6 << 4)\n        sleep(0.01)\n        # close w/ freeze: outputs should not be reset (usually 0b0110)\n        gpio.close(True)\n\n    def test_gpio_values(self):\n        \"\"\"Simple test to demonstrate bit-banging.\n        \"\"\"\n        if self.skip_loopback:\n            raise SkipTest('Skip loopback test on multiport device')\n        direction = 0xFF & ~((1 << 4) - 1)  # 4 Out, 4 In\n        gpio = GpioAsyncController()\n        gpio.configure(self.url, direction=direction, frequency=1e6,\n                       initial=0x0)\n        port = gpio.get_gpio()  # useless, for API duck typing\n        # legacy API: peek mode, 1 byte\n        ingress = port.read()\n        self.assertIsInstance(ingress, int)\n        # peek mode always gives a single byte output\n        ingress = port.read(peek=True)\n        self.assertIsInstance(ingress, int)\n        # stream mode always gives a bytes buffer\n        port.write([0xaa for _ in range(256)])\n        ingress = port.read(100, peek=False, noflush=False)\n        self.assertIsInstance(ingress, bytes)\n        if not VirtLoader:\n            # the virtual task may sometimes not be triggered soon enough\n            self.assertGreater(len(ingress), 2)\n        # direct mode is not available with multi-byte mode\n        self.assertRaises(ValueError, port.read, 3, True)\n        ingress = port.read(3)\n        self.assertIsInstance(ingress, bytes)\n        if not VirtLoader:\n            # the virtual task may sometimes not be triggered soon enough\n            self.assertGreater(len(ingress), 0)\n        self.assertLessEqual(len(ingress), 3)\n        port.write(0x00)\n        port.write(0xFF)\n        # only 8 bit values are accepted\n        self.assertRaises(ValueError, port.write, 0x100)\n        port.write([0x00, 0xFF, 0x00])\n        port.write(bytes([0x00, 0xFF, 0x00]))\n        # only 8 bit values are accepted\n        self.assertRaises(ValueError, port.write, [0x00, 0x100, 0x00])\n        # check direction API\n        port.set_direction(0xFF, 0xFF & ~direction)\n        gpio.close()\n\n    def test_gpio_initial(self):\n        \"\"\"Check initial values.\n        \"\"\"\n        if self.skip_loopback:\n            raise SkipTest('Skip initial test on multiport device')\n        if not self.loader:\n            raise SkipTest('Skip initial test on physical device')\n        direction = 0xFF & ~((1 << 4) - 1)  # 4 Out, 4 In\n        vftdi = self.loader.get_virtual_ftdi(1, 1)\n        vport = vftdi.get_port(1)\n        gpio = GpioAsyncController()\n        for initial in (0xaf, 0xf0, 0x13, 0x00):\n            gpio.configure(self.url, direction=direction, frequency=1e6,\n                           initial=initial)\n            expect = (initial & 0xF0) | (initial >> 4)\n            self.assertEqual(vport.gpio, expect)\n            gpio.close()\n\n    def test_gpio_loopback(self):\n        \"\"\"Check I/O.\n        \"\"\"\n        if self.skip_loopback:\n            raise SkipTest('Skip loopback test on multiport device')\n        gpio = GpioAsyncController()\n        direction = 0xFF & ~((1 << 4) - 1)  # 4 Out, 4 In\n        gpio.configure(self.url, direction=direction, frequency=800000)\n        for out in range(16):\n            # print(f'Write {out:04b} -> {out << 4:08b}')\n            gpio.write(out << 4)\n            fback = gpio.read()\n            lsbs = fback & ~direction\n            msbs = fback >> 4\n            # check inputs match outputs\n            self.assertEqual(lsbs, out)\n            # check level of outputs match the ones written\n            self.assertEqual(msbs, out)\n        outs = list((out & 0xf) << 4 for out in range(1000))\n        gpio.write(outs)\n        gpio.ftdi.read_data(512)\n        for _ in range(len(outs)):\n            _ = gpio.read(14)\n        last = outs[-1] >> 4\n        for _ in range(10):\n            fbacks = gpio.read(1000)\n            for fback in fbacks:\n                lsbs = fback & ~direction\n                msbs = fback >> 4\n                # check inputs match last output\n                self.assertEqual(lsbs, last)\n                # check level of output match the last written\n                self.assertEqual(msbs, last)\n        gpio.close()\n\n    def test_gpio_baudate(self):\n        # this test requires an external device (logic analyser or scope) to\n        # check the bitbang read and bitbang write signal (BB_RD, BB_WR) and\n        # mesure their frequency. The EEPROM should be configured to enable\n        # those signal on some of the CBUS pins, for example.\n        gpio = GpioAsyncController()\n        direction = 0xFF & ~((1 << 4) - 1)  # 4 Out, 4 In\n        gpio.configure(self.url, direction=direction)\n        buf = bytes([0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00])\n        freqs = [50e3, 200e3, 1e6, 3e6]\n        if gpio.ftdi.is_H_series:\n            freqs.extend([6e6, 10e6, 12e6])\n        gpio.read(128)\n        for freq in freqs:\n            # set the bitbang refresh rate\n            gpio.set_frequency(freq)\n            self.assertEqual(gpio.frequency, freq)\n            # be sure to leave enough time to purge buffers (HW FIFO) or\n            # the frequency changes occur on the current buffer...\n            gpio.write(buf)\n            gpio.read(128)\n            sleep(0.01)\n        gpio.close()\n\n\nclass GpioSyncTestCase(FtdiTestCase):\n    \"\"\"FTDI Synchrnous GPIO driver test case.\n\n       Please ensure that the HW you connect to the FTDI port A does match\n       the encoded configuration. Check your HW setup before running this test\n       as it might damage your HW. You've been warned.\n\n       Low nibble is used as input, high nibble is used as output. They should\n       be interconnected as follow:\n\n       * b0 should be connected to b4\n       * b1 should be connected to b5\n       * b2 should be connected to b6\n       * b3 should be connected to b7\n\n       Note that this test cannot work with LC231X board, as FTDI is stupid\n       enough to add ubidirectionnal output buffer on DTR and RTS, so both\n       nibbles have at lest on output pin...\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        if VirtLoader:\n            cls.loader = VirtLoader()\n            with open('pyftdi/tests/resources/ft232r.yaml', 'rb') as yfp:\n                cls.loader.load(yfp)\n            vftdi = cls.loader.get_virtual_ftdi(1, 1)\n            vport = vftdi.get_port(1)\n            # create virtual connections as real HW\n            in_pins = [vport[pos] for pos in range(4)]\n            out_pins = [vport[pos] for pos in range(4, 8)]\n            for in_pin, out_pin in zip(in_pins, out_pins):\n                out_pin.connect_to(in_pin)\n        if cls.url == 'ftdi:///1':\n            # assumes that if not specific device is used, and a multiport\n            # device is connected, there is no loopback wires between pins of\n            # the same port. This hack allows to run the same test with a\n            # FT232H, then a FT2232H for ex, to that with two test sessions\n            # the whole test set is run. If a specific device is selected\n            # assume the HW always match the expected configuration.\n            ftdi = Ftdi()\n            ftdi.open_from_url(cls.url)\n            count = ftdi.device_port_count\n            ftdi.close()\n            cls.skip_loopback = count > 1\n        else:\n            cls.skip_loopback = False\n\n    def test_gpio_values(self):\n        \"\"\"Simple test to demonstrate bit-banging.\n        \"\"\"\n        if self.skip_loopback:\n            raise SkipTest('Skip loopback test on multiport device')\n        direction = 0xFF & ~((1 << 4) - 1)  # 4 Out, 4 In\n        gpio = GpioSyncController()\n        gpio.configure(self.url, direction=direction, initial=0xee)\n        outs = bytes([(out & 0xf) << 4 for out in range(1000)])\n        ins = gpio.exchange(outs)\n        exp_in_count = min(len(outs), gpio.ftdi.fifo_sizes[0])\n        self.assertEqual(len(ins), exp_in_count)\n        last = None\n        for sout, sin in zip(outs, ins):\n            if last is not None:\n                # output nibble\n                sin_out = sin >> 4\n                # input nibble\n                sin_in = sin & 0xF\n                # check inputs match last output\n                self.assertEqual(sin_out, last)\n                # check level of output match the last written\n                self.assertEqual(sin_in, last)\n            # an IN sample if captured on the next clock of the OUT sample\n            # keep the MSB nibble, i.e. the nibble configured as output\n            last = sout >> 4\n        gpio.close()\n\n    def test_gpio_baudate(self):\n        # this test requires an external device (logic analyser or scope) to\n        # check the bitbang read and bitbang write signal (BB_RD, BB_WR) and\n        # mesure their frequency. The EEPROM should be configured to enable\n        # those signal on some of the CBUS pins, for example.\n        gpio = GpioSyncController()\n        direction = 0xFF & ~((1 << 4) - 1)  # 4 Out, 4 In\n        gpio.configure(self.url, direction=direction)\n        buf = bytes([0xf0, 0x00] * 64)\n        freqs = [50e3, 200e3, 1e6, 3e6]\n        if gpio.ftdi.is_H_series:\n            freqs.extend([6e6, 10e6, 12e6])\n        for freq in freqs:\n            # set the bitbang refresh rate\n            gpio.set_frequency(freq)\n            self.assertEqual(gpio.frequency, freq)\n            # be sure to leave enough time to purge buffers (HW FIFO) or\n            # the frequency changes occur on the current buffer...\n            gpio.exchange(buf)\n            sleep(0.01)\n        gpio.close()\n\n\nclass GpioMultiportTestCase(FtdiTestCase):\n    \"\"\"FTDI GPIO test for multi-port FTDI devices,\n       i.e. FT2232H/FT4232H/FT4232HA.\n\n       Please ensure that the HW you connect to the FTDI port A does match\n       the encoded configuration. Check your HW setup before running this test\n       as it might damage your HW. You've been warned.\n\n       First port is used as input, second port is used as output. They should\n       be interconnected as follow:\n\n       * AD0 should be connected to BD0\n       * AD1 should be connected to BD1\n       * AD2 should be connected to BD2\n       * AD3 should be connected to BD3\n       * AD0 should be connected to BD0\n       * AD1 should be connected to BD1\n       * AD2 should be connected to BD2\n       * AD3 should be connected to BD3\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        if VirtLoader:\n            cls.loader = VirtLoader()\n            with open('pyftdi/tests/resources/ft2232h.yaml', 'rb') as yfp:\n                cls.loader.load(yfp)\n            vftdi = cls.loader.get_virtual_ftdi(1, 1)\n            vport1 = vftdi.get_port(1)\n            vport2 = vftdi.get_port(2)\n            # create virtual connections as real HW\n            in_pins = [vport1[pos] for pos in range(8)]\n            out_pins = [vport2[pos] for pos in range(8)]\n            for in_pin, out_pin in zip(in_pins, out_pins):\n                out_pin.connect_to(in_pin)\n        ftdi = Ftdi()\n        ftdi.open_from_url(cls.url)\n        count = ftdi.device_port_count\n        pos = ftdi.port_index\n        ftdi.close()\n        if pos != 1:\n            raise ValueError(\"FTDI interface should be the device's first\")\n        if count < 2:\n            raise SkipTest('FTDI device is not a multi-port device')\n        url = cls.url[:-1]\n        cls.urls = [f'{url}1', f'{url}2']\n\n    def test_gpio_peek(self):\n        \"\"\"Check I/O.\n        \"\"\"\n        gpio_in, gpio_out = GpioAsyncController(), GpioAsyncController()\n        gpio_in.configure(self.urls[0], direction=0x00, frequency=1e6)\n        gpio_out.configure(self.urls[1], direction=0xFF, frequency=1e6)\n        for out in range(256):\n            gpio_out.write(out)\n            outv = gpio_out.read()\n            inv = gpio_in.read()\n            # check inputs match outputs\n            self.assertEqual(inv, out)\n            # check level of outputs match the ones written\n            self.assertEqual(outv, out)\n        gpio_in.close()\n        gpio_out.close()\n\n    def test_gpio_stream(self):\n        \"\"\"Check I/O streaming\n        \"\"\"\n        if VirtLoader:\n            # this would require to synchronize virtual clock between all ports\n            # which is not supported by the virtual framework\n            raise SkipTest('Skip gpio stream with virtual device')\n        gpio_in, gpio_out = GpioAsyncController(), GpioAsyncController()\n        gpio_in.configure(self.urls[0], direction=0x00, frequency=1e4)\n        gpio_out.configure(self.urls[1], direction=0xFF, frequency=1e4)\n        outs = bytes(range(256))\n        gpio_out.write(outs)\n        # read @ same speed (and same clock source, so no jitter), flushing\n        # the byffer which has been filled since the port has been opened\n        ins = gpio_in.read(len(outs))\n        qout = deque(outs)\n        ifirst = ins[0]\n        # the inout stream should be a copy of the output stream, minus a\n        # couple of missing samples that did not get captured while output\n        # was streaming but read command has not been yet received.\n        while qout:\n            if qout[0] == ifirst:\n                break\n            qout.popleft()\n        # offset is the count of missed bytes\n        offset = len(ins)-len(qout)\n        self.assertGreater(offset, 0)  # no more output than input\n        self.assertLess(offset, 16)  # seems to be in the 6..12 range\n        # print('Offset', offset)\n        # check that the remaining sequence match\n        for sout, sin in zip(qout, ins):\n            # check inputs match outputs\n            self.assertEqual(sout, sin)\n        gpio_in.close()\n        gpio_out.close()\n\n    def test_direction(self):\n        b5 = 1 << 5\n        for controller in (GpioAsyncController,\n                           GpioSyncController,\n                           GpioMpsseController):\n            gpio_in, gpio_out = controller(), controller()\n            gpio_in.configure(self.urls[0], direction=0x00, frequency=1e6,\n                              debug=self.debug)\n            gpio_out.configure(self.urls[1], direction=0xFF, frequency=1e6,\n                               debug=self.debug)\n            for direction in None, 0xFF, b5, 0xF0:\n                if direction is not None:\n                    gpio_out.set_direction(0xFF, direction)\n                for out in 0 << 5, 1 << 5:\n                    if controller != GpioSyncController:\n                        gpio_out.write(out)\n                        outp = gpio_out.read(1)\n                        inp = gpio_in.read(1)\n                        if controller == GpioMpsseController:\n                            outp = outp[0]\n                            inp = inp[0]\n                    else:\n                        # write twice the output value, only the second value\n                        # matters (see Sync bitbang for details)\n                        outp = gpio_out.exchange(bytes([out, out]))[1]\n                        # write anything as we just need the input value which\n                        # is sampled on each write\n                        inp = gpio_in.exchange(b'\\x00')[0]\n                    self.assertEqual(outp & b5, out)\n                    self.assertEqual(inp & b5, out)\n\n\nclass GpioMpsseTestCase(FtdiTestCase):\n    \"\"\"FTDI GPIO test for 16-bit port FTDI devices, i.e. FT2232H.\n\n       Please ensure that the HW you connect to the FTDI port A does match\n       the encoded configuration. Check your HW setup before running this test\n       as it might damage your HW. You've been warned.\n\n       First port is used as input, second port is used as output. They should\n       be interconnected as follow:\n\n       * AD0 should be connected to BD0\n       * AD1 should be connected to BD1\n       * AD2 should be connected to BD2\n       * AD3 should be connected to BD3\n       * AD0 should be connected to BD0\n       * AD1 should be connected to BD1\n       * AD2 should be connected to BD2\n       * AD3 should be connected to BD3\n       * AC0 should be connected to BC0\n       * AC1 should be connected to BC1\n       * AC2 should be connected to BC2\n       * AC3 should be connected to BC3\n       * AC0 should be connected to BC0\n       * AC1 should be connected to BC1\n       * AC2 should be connected to BC2\n       * AC3 should be connected to BC3\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        if VirtLoader:\n            cls.loader = VirtLoader()\n            with open('pyftdi/tests/resources/ft2232h.yaml', 'rb') as yfp:\n                cls.loader.load(yfp)\n            vftdi = cls.loader.get_virtual_ftdi(1, 1)\n            vport1 = vftdi.get_port(1)\n            vport2 = vftdi.get_port(2)\n            # create virtual connections as real HW\n            in_pins = [vport1[pos] for pos in range(16)]\n            out_pins = [vport2[pos] for pos in range(16)]\n            for in_pin, out_pin in zip(in_pins, out_pins):\n                out_pin.connect_to(in_pin)\n            # prevent from using the tracer twice (Ftdi & VirtualFtdi)\n            cls.debug_mpsse = False\n        else:\n            cls.debug_mpsse = cls.debug\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        ftdi = Ftdi()\n        ftdi.open_from_url(url)\n        count = ftdi.device_port_count\n        width = ftdi.port_width\n        ftdi.close()\n        if count < 2:\n            raise SkipTest('FTDI device is not a multi-port device')\n        if width < 2:\n            raise SkipTest('FTDI device does not support wide ports')\n        url = url[:-1]\n        cls.urls = [f'{url}1', f'{url}2']\n\n    def test_default_gpio(self):\n        \"\"\"Check I/O.\n        \"\"\"\n        gpio_in, gpio_out = GpioMpsseController(), GpioMpsseController()\n        gpio_in.configure(self.urls[0], direction=0x0000, frequency=10e6,\n                          debug=self.debug_mpsse)\n        gpio_out.configure(self.urls[1], direction=0xFFFF, frequency=10e6,\n                           debug=self.debug_mpsse)\n        for out in range(3, 0x10000, 29):\n            gpio_out.write(out)\n            outv = gpio_out.read()[0]\n            inv = gpio_in.read()[0]\n            # check inputs match outputs\n            self.assertEqual(inv, out)\n            # check level of outputs match the ones written\n            self.assertEqual(outv, out)\n        gpio_in.close()\n        gpio_out.close()\n\n    def test_peek_gpio(self):\n        \"\"\"Check I/O peeking\n        \"\"\"\n        gpio_in, gpio_out = GpioMpsseController(), GpioMpsseController()\n        gpio_in.configure(self.urls[0], direction=0xFF00, frequency=10e6,\n                          debug=self.debug)\n        gpio_out.configure(self.urls[1], direction=0x00FF, frequency=10e6,\n                           debug=self.debug)\n        for out in range(256):\n            gpio_out.write(out)\n            outv = gpio_out.read()[0]\n            inv = gpio_in.read(peek=True)\n            # check inputs match outputs\n            self.assertEqual(inv, out)\n            # check level of outputs match the ones written\n            self.assertEqual(outv, out)\n        gpio_in.close()\n        gpio_out.close()\n\n    def test_stream_gpio(self):\n        \"\"\"Check I/O streaming.\n\n           Beware this test is CPU intensive w/ virtual framework\n        \"\"\"\n        gpio_in, gpio_out = GpioMpsseController(), GpioMpsseController()\n        gpio_in.configure(self.urls[0], direction=0x0000, frequency=10e6,\n                          debug=self.debug)\n        gpio_out.configure(self.urls[1], direction=0xFFFF, frequency=10e6,\n                           debug=self.debug)\n        outv = list(range(0, 0x10000, 29))\n        max_count = min(gpio_out.ftdi.fifo_sizes[0],\n                        gpio_in.ftdi.fifo_sizes[1])//2  # 2 bytes/value\n        outv = outv[:max_count]\n        gpio_out.write(outv)\n        inv = gpio_in.read(len(outv))\n        # for now, it is hard to test value exactness\n        self.assertEqual(len(outv), len(inv))\n        gpio_in.close()\n        gpio_out.close()\n\n\ndef suite():\n    suite_ = TestSuite()\n    suite_.addTest(TestLoader().loadTestsFromModule(modules[__name__]))\n    return suite_\n\n\ndef virtualize():\n    if not to_bool(environ.get('FTDI_VIRTUAL', 'off')):\n        return\n    # pylint: disable=import-outside-toplevel\n    from pyftdi.usbtools import UsbTools\n    # Force PyUSB to use PyFtdi test framework for USB backends\n    UsbTools.BACKENDS = ('backend.usbvirt', )\n    # Ensure the virtual backend can be found and is loaded\n    backend = UsbTools.find_backend()\n    try:\n        # obtain the loader class associated with the virtual backend\n        global VirtLoader\n        VirtLoader = backend.create_loader()\n    except AttributeError as exc:\n        raise AssertionError('Cannot load virtual USB backend') from exc\n\n\ndef setup_module():\n    # pylint: disable=import-outside-toplevel\n    from doctest import testmod\n    testmod(modules[__name__])\n    debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n    if debug:\n        formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)-7s'\n                                      ' %(name)-20s [%(lineno)4d] %(message)s',\n                                      '%H:%M:%S')\n    else:\n        formatter = logging.Formatter('%(message)s')\n    level = environ.get('FTDI_LOGLEVEL', 'warning').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    FtdiLogger.set_level(loglevel)\n    FtdiLogger.set_formatter(formatter)\n    virtualize()\n\n\ndef main():\n    setup_module()\n    try:\n        ut_main(defaultTest='suite')\n    except KeyboardInterrupt:\n        pass\n\n\nif __name__ == '__main__':\n    # Useful environment variables:\n    #  FTDI_DEVICE: a specific FTDI URL, default to ftdi:///1\n    #  FTDI_LOGLEVEL: a Logger debug level, to define log verbosity\n    #  FTDI_DEBUG: to enable/disable debug mode\n    #  FTDI_VIRTUAL: to use a virtual device rather than a physical device\n    main()\n"
  },
  {
    "path": "pyftdi/tests/i2c.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"I2C unit tests.\"\"\"\n\n# Copyright (c) 2017-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nimport logging\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom binascii import hexlify\nfrom doctest import testmod\nfrom os import environ\nfrom sys import modules, stdout\nfrom time import time as now\nfrom pyftdi import FtdiLogger\nfrom pyftdi.i2c import I2cController, I2cIOError\nfrom pyftdi.misc import pretty_size\n\n# pylint: disable=attribute-defined-outside-init\n# pylint: disable=missing-docstring\n\n\nclass I2cTca9555TestCase(TestCase):\n    \"\"\"Simple test for a TCA9555 device on I2C bus @ address 0x21\n    \"\"\"\n\n    def test(self):\n        self._i2c = I2cController()\n        self._open()\n        self._read_it()\n        self._write_it()\n        self._close()\n\n    def _open(self):\n        \"\"\"Open an I2c connection to a slave\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        self._i2c.configure(url)\n\n    def _read_it(self):\n        port = self._i2c.get_port(0x21)\n        port.exchange([0x04], 1)\n\n    def _write_it(self):\n        port = self._i2c.get_port(0x21)\n        port.write_to(0x06, b'\\x00')\n        port.write_to(0x02, b'\\x55')\n        port.read_from(0x00, 1)\n\n    def _close(self):\n        \"\"\"Close the I2C connection\"\"\"\n        self._i2c.terminate()\n\n\nclass I2cAccelTestCase(TestCase):\n    \"\"\"Basic test for an ADXL345 device on I2C bus @ address 0x53\n    \"\"\"\n\n    def test(self):\n        self._i2c = I2cController()\n        self._open()\n        self._read_device_id()\n        self._close()\n\n    def _open(self):\n        \"\"\"Open an I2c connection to a slave\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        self._i2c.configure(url)\n\n    def _read_device_id(self):\n        port = self._i2c.get_port(0x53)\n        device_id = port.exchange([0x00], 1)\n        hex_device_id = hexlify(device_id).decode()\n        print('DEVICE ID:', hex_device_id)\n        return hex_device_id\n\n    def _close(self):\n        \"\"\"Close the I2C connection\"\"\"\n        self._i2c.terminate()\n\n\nclass I2cReadTestCase(TestCase):\n    \"\"\"Simple test to read a sequence of bytes I2C bus @ address 0x36\n    \"\"\"\n\n    def test(self):\n        self._i2c = I2cController()\n        self._open()\n        self._read()\n        self._close()\n\n    def _open(self):\n        \"\"\"Open an I2c connection to a slave\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        self._i2c.configure(url)\n\n    def _read(self):\n        address = environ.get('I2C_ADDRESS', '0x36').lower()\n        addr = int(address, 16 if address.startswith('0x') else 10)\n        port = self._i2c.get_port(addr)\n        data = port.read(32)\n        print(hexlify(data).decode(), data.decode('utf8', errors='replace'))\n\n    def _close(self):\n        \"\"\"Close the I2C connection\"\"\"\n        self._i2c.terminate()\n\n\nclass I2cEepromTestCase(TestCase):\n    \"\"\"Simple test to read a sequence of bytes I2C bus @ address 0x50,\n       from an I2C data flash\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        address = environ.get('I2C_ADDRESS', '0x50').lower()\n        cls.address = int(address, 16 if address.startswith('0x') else 10)\n\n    def setUp(self):\n        self._i2c = I2cController()\n        self._i2c.configure(self.url, frequency=400e3,\n                            clockstretching=False, debug=False, rdoptim=True)\n\n    def tearDown(self):\n        self._i2c.terminate()\n\n    def test_short(self):\n        port = self._i2c.get_port(self.address)\n        # select start address\n        port.write(b'\\x00\\x08')\n        data = port.read(4)\n        text = data.decode('utf8', errors='replace')\n        # print(hexlify(data).decode(), text)\n        self.assertEqual(text, 'Worl')\n\n    def test_long(self):\n        port = self._i2c.get_port(self.address)\n        # select start address\n        size = 4096\n        port.write(b'\\x00\\x00')\n        start = now()\n        data = port.read(size)\n        stop = now()\n        text = data.decode('utf8', errors='replace')\n        delta = stop-start\n        byterate = pretty_size(len(data)/delta)\n        print(f'Exec time: {1000*delta:.3f} ms, {byterate}/s')\n        self.assertEqual(text[8:12], 'Worl')\n\n\nclass I2cReadGpioTestCase(TestCase):\n    \"\"\"Simple test to exercise I2C + GPIO mode.\n\n       A slave device (such as EEPROM) should be connected to the I2C bus\n       at either the default 0x36 address or defined with the I2C_ADDRESS\n       ebvironment variable.\n\n       AD0: SCL, AD1+AD2: SDA, AD3 connected to AC0, AD4 connected to AC1\n\n       I2C EEPROM is read, and values are written to AD3:AD4 and read back\n       from AC0:AC1.\n    \"\"\"\n\n    GPIO_WIDTH = 2  # use 2 GPIOs for output, 2 GPIOs for input (loopback)\n    GPIO_OUT_OFFSET = 3  # GPIO output are b3..b4\n    GPIO_IN_OFFSET = 8  # GPIO input are b8..b9\n\n    def test(self):\n        self._i2c = I2cController()\n        self._open()\n        self._execute_sequence()\n        self._execute_interleave()\n        self._close()\n\n    def _open(self):\n        \"\"\"Open an I2c connection to a slave\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        self._i2c.configure(url)\n        address = environ.get('I2C_ADDRESS', '0x36').lower()\n        addr = int(address, 16 if address.startswith('0x') else 10)\n        self._port = self._i2c.get_port(addr)\n        self._gpio = self._i2c.get_gpio()\n        mask = (1 << self.GPIO_WIDTH) - 1\n        self._gpio.set_direction(mask << self.GPIO_OUT_OFFSET |\n                                 mask << self.GPIO_IN_OFFSET,\n                                 mask << self.GPIO_OUT_OFFSET)\n\n    def _execute_sequence(self):\n        # reset EEPROM read pointer position\n        self._port.write(b'\\x00\\x00')\n        ref = self._port.read(32)\n        for dout in range(1 << self.GPIO_WIDTH):\n            self._gpio.write(dout << self.GPIO_OUT_OFFSET)\n            din = self._gpio.read() >> self.GPIO_IN_OFFSET\n            if dout != din:\n                raise AssertionError(f'GPIO mismatch {din:04x} != {dout:04x}')\n        self._gpio.write(0)\n        # reset EEPROM read pointer position\n        self._port.write(b'\\x00\\x00')\n        data = self._port.read(32)\n        if data != ref:\n            raise AssertionError(\"I2C data mismatch\")\n\n    def _execute_interleave(self):\n        # reset EEPROM read pointer position\n        self._port.write(b'\\x00\\x00')\n        ref = self._port.read(32)\n        for dout in range(1 << self.GPIO_WIDTH):\n            self._gpio.write(dout << self.GPIO_OUT_OFFSET)\n            # reset EEPROM read pointer position\n            self._port.write(b'\\x00\\x00')\n            data = self._port.read(32)\n            din = self._gpio.read() >> self.GPIO_IN_OFFSET\n            if data != ref:\n                raise AssertionError(\"I2C data mismatch\")\n            if dout != din:\n                raise AssertionError(f'GPIO mismatch {din:04x} != {dout:04x}')\n        self._gpio.write(0)\n\n    def _close(self):\n        \"\"\"Close the I2C connection\"\"\"\n        self._i2c.terminate()\n\n\nclass I2cClockStrechingGpioTestCase(TestCase):\n    \"\"\"Simple test to check clock stretching cannot be overwritten with\n       GPIOs.\n    \"\"\"\n\n    def test(self):\n        self._i2c = I2cController()\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        self._i2c.configure(url, clockstretching=True)\n        gpio = self._i2c.get_gpio()\n        self.assertRaises(I2cIOError, gpio.set_direction, 1 << 7, 0)\n\n\nclass I2cDualMasterTestCase(TestCase):\n    \"\"\"Check the behaviour of 2 I2C masters. Requires a multi port FTDI device,\n       i.e. FT2232H, FT4232H or FT4232HA. See issue #159.\n    \"\"\"\n\n    def test(self):\n        url1 = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        i2c1 = I2cController()\n        i2c1.configure(url1, frequency=100000)\n        url2 = f'{url1[:-1]}{int(url1[-1])+1}'\n        i2c2 = I2cController()\n        i2c2.configure(url2, frequency=100000)\n        port = i2c2.get_port(0x76)\n        print(port.read_from(0x00, 2))\n        print(port.read_from(0x00, 2))\n\n\nclass I2cIssue143TestCase(TestCase):\n    \"\"\"#143.\n    \"\"\"\n\n    def test(self):\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        address = environ.get('I2C_ADDRESS', '0x50').lower()\n        addr = int(address, 16 if address.startswith('0x') else 10)\n        i2c = I2cController()\n        i2c.configure(url)\n        slave = i2c.get_port(addr)\n        gpio = i2c.get_gpio()\n        gpio.set_direction(0x0010, 0x0010)\n        gpio.write(0)\n        gpio.write(1 << 4)\n        gpio.write(0)\n        slave.write([0x12, 0x34])\n        gpio.write(0)\n        gpio.write(1 << 4)\n        gpio.write(0)\n\n\ndef suite():\n    \"\"\"FTDI I2C driver test suite\n\n       Simple test to demonstrate I2C.\n\n       Please ensure that the HW you connect to the FTDI port A does match\n       the encoded configuration. GPIOs can be driven high or low, so check\n       your HW setup before running this test as it might damage your HW.\n\n       Do NOT run this test if you use FTDI port A as an UART or SPI\n       bridge -or any unsupported setup!! You've been warned.\n    \"\"\"\n    suite_ = TestSuite()\n    loader = TestLoader()\n    mod = modules[__name__]\n    tests = (  # 'I2cTca9555', 'I2cAccel', 'I2cRead',\n             'I2cEeprom',  # 'I2cReadGpio',\n             'I2cClockStrechingGpio',  # 'I2cDualMaster',\n             'I2cIssue143')\n    for testname in tests:\n        testcase = getattr(mod, f'{testname}TestCase')\n        suite_.addTest(loader.loadTestsFromTestCase(testcase))\n    return suite_\n\n\ndef main():\n    testmod(modules[__name__])\n    FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    level = environ.get('FTDI_LOGLEVEL', 'info').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.set_level(loglevel)\n    ut_main(defaultTest='suite')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/tests/jtag.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"JTAG unit test.\"\"\"\n\n# Copyright (c) 2011-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nfrom os import environ\nfrom sys import modules\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom pyftdi.jtag import JtagEngine, JtagTool\nfrom pyftdi.bits import BitSequence\n\n# pylint: disable=missing-docstring\n\n\n# Should match the tested device\nJTAG_INSTR = {'SAMPLE': BitSequence('0001', msb=True, length=4),\n              'PRELOAD': BitSequence('0001', msb=True, length=4),\n              'IDCODE': BitSequence('0100', msb=True, length=4),\n              'BYPASS': BitSequence('1111', msb=True, length=4)}\n\n\nclass JtagTestCase(TestCase):\n\n    def setUp(self):\n        url = environ.get('FTDI_DEVICE', 'ftdi://ftdi:2232h/1')\n        self.jtag = JtagEngine(trst=True, frequency=3E6)\n        self.jtag.configure(url)\n        self.jtag.reset()\n        self.tool = JtagTool(self.jtag)\n\n    def tearDown(self):\n        del self.jtag\n\n    def test_idcode_reset(self):\n        \"\"\"Read the IDCODE right after a JTAG reset\"\"\"\n        self.jtag.reset()\n        idcode = self.jtag.read_dr(32)\n        self.jtag.go_idle()\n        print(f'IDCODE (reset): 0x{int(idcode):x}')\n\n    def test_idcode_sequence(self):\n        \"\"\"Read the IDCODE using the dedicated instruction\"\"\"\n        instruction = JTAG_INSTR['IDCODE']\n        self.jtag.write_ir(instruction)\n        idcode = self.jtag.read_dr(32)\n        self.jtag.go_idle()\n        print(f'IDCODE (idcode): 0x{int(idcode):08x}')\n\n    def test_idcode_shift_register(self):\n        \"\"\"Read the IDCODE using the dedicated instruction with\n           shift_and_update_register\"\"\"\n        instruction = JTAG_INSTR['IDCODE']\n        self.jtag.change_state('shift_ir')\n        retval = self.jtag.shift_and_update_register(instruction)\n        print(f'retval: 0x{int(retval):x}')\n        self.jtag.go_idle()\n        self.jtag.change_state('shift_dr')\n        idcode = self.jtag.shift_and_update_register(BitSequence('0'*32))\n        self.jtag.go_idle()\n        print(f'IDCODE (idcode): 0x{int(idcode):08x}')\n\n    def test_bypass_shift_register(self):\n        \"\"\"Test the BYPASS instruction using shift_and_update_register\"\"\"\n        instruction = JTAG_INSTR['BYPASS']\n        self.jtag.change_state('shift_ir')\n        retval = self.jtag.shift_and_update_register(instruction)\n        print(f'retval: 0x{int(retval):x}')\n        self.jtag.go_idle()\n        self.jtag.change_state('shift_dr')\n        in_ = BitSequence('011011110000'*2, length=24)\n        out = self.jtag.shift_and_update_register(in_)\n        self.jtag.go_idle()\n        print(f'BYPASS sent: {in_}, received: {out} '\n              f' (should be left shifted by one)')\n\n    def _test_detect_ir_length(self):\n        \"\"\"Detect the instruction register length\"\"\"\n        self.jtag.go_idle()\n        self.jtag.capture_ir()\n        self.tool.detect_register_size()\n\n\ndef suite():\n    suite_ = TestSuite()\n    suite_.addTest(TestLoader().loadTestsFromModule(modules[__name__]))\n    return suite_\n\n\nif __name__ == '__main__':\n    ut_main(defaultTest='suite')\n"
  },
  {
    "path": "pyftdi/tests/mockusb.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright (c) 2020-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n\n# pylint: disable=empty-docstring\n# pylint: disable=global-statement\n# pylint: disable=invalid-name\n# pylint: disable=missing-docstring\n# pylint: disable=too-many-locals\n\nimport logging\nfrom collections import defaultdict\nfrom contextlib import redirect_stdout\nfrom doctest import testmod\nfrom io import StringIO\nfrom os import environ\nfrom string import ascii_letters\nfrom sys import modules, stdout\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom urllib.parse import urlsplit\nfrom pyftdi import FtdiLogger\nfrom pyftdi.eeprom import FtdiEeprom\nfrom pyftdi.ftdi import Ftdi, FtdiMpsseError\nfrom pyftdi.gpio import GpioController\nfrom pyftdi.misc import to_bool\nfrom pyftdi.serialext import serial_for_url\nfrom pyftdi.usbtools import UsbTools\n\n# MockLoader is assigned in ut_main\nMockLoader = None\n\n\nclass FtdiTestCase(TestCase):\n    \"\"\"Common features for all tests.\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        cls.loader = MockLoader()\n        cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off'), permissive=False)\n\n    @classmethod\n    def tearDownClass(cls):\n        if cls.loader:\n            cls.loader.unload()\n\n    def setUp(self):\n        if self.debug:\n            print('.'.join(self.id().split('.')[-2:]))\n\n\nclass MockUsbToolsTestCase(FtdiTestCase):\n    \"\"\"Test UsbTools APIs.\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ftmany.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_enumerate(self):\n        \"\"\"Enumerate FTDI devices.\"\"\"\n        ftdis = [(0x403, pid)\n                 for pid in (0x6001, 0x6010, 0x6011, 0x6014, 0x6015)]\n        count = len(UsbTools.find_all(ftdis))\n        self.assertEqual(count, 6)\n\n    def test_device(self):\n        \"\"\"Access and release FTDI device.\"\"\"\n        ftdis = [(0x403, 0x6001)]\n        ft232rs = UsbTools.find_all(ftdis)\n        self.assertEqual(len(ft232rs), 1)\n        devdesc, ifcount = ft232rs[0]\n        self.assertEqual(ifcount, 1)\n        dev = UsbTools.get_device(devdesc)\n        self.assertIsNotNone(dev)\n        UsbTools.release_device(dev)\n\n    def test_string(self):\n        \"\"\"Retrieve a string from its identifier.\"\"\"\n        ftdis = [(0x403, 0x6010)]\n        ft2232h = UsbTools.find_all(ftdis)[0]\n        devdesc, _ = ft2232h\n        dev = UsbTools.get_device(devdesc)\n        serialn = UsbTools.get_string(dev, dev.iSerialNumber)\n        self.assertEqual(serialn, 'FT2DEF')\n\n    def test_list_devices(self):\n        \"\"\"List FTDI devices.\"\"\"\n        vid = 0x403\n        vids = {'ftdi': vid}\n        pids = {\n            vid: {\n                '230x': 0x6015,\n                '232r': 0x6001,\n                '232h': 0x6014,\n                '2232h': 0x6010,\n                '4232h': 0x6011,\n                '4232ha': 0x6048,\n            }\n        }\n        devs = UsbTools.list_devices('ftdi:///?', vids, pids, vid)\n        self.assertEqual(len(devs), 7)\n        ifmap = {\n            0x6001: 1,\n            0x6010: 2,\n            0x6011: 4,\n            0x6014: 1,\n            0x6015: 1,\n            0x6048: 4\n        }\n        for dev, desc in devs:\n            strings = UsbTools.build_dev_strings('ftdi', vids, pids,\n                                                 [(dev, desc)])\n            self.assertEqual(len(strings), ifmap[dev.pid])\n            for url, _ in strings:\n                parts, _ = UsbTools.parse_url(url, 'ftdi', vids, pids, vid)\n                self.assertEqual(parts.vid, dev.vid)\n                self.assertEqual(parts.pid, dev.pid)\n                self.assertEqual(parts.bus, dev.bus)\n                self.assertEqual(parts.address, dev.address)\n                self.assertEqual(parts.sn, dev.sn)\n        devs = UsbTools.list_devices('ftdi://:232h/?', vids, pids, vid)\n        self.assertEqual(len(devs), 2)\n        devs = UsbTools.list_devices('ftdi://:2232h/?', vids, pids, vid)\n        self.assertEqual(len(devs), 1)\n\n\nclass MockFtdiDiscoveryTestCase(FtdiTestCase):\n    \"\"\"Test FTDI device discovery APIs.\n       These APIs are FTDI wrappers for UsbTools APIs.\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ftmany.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_list_devices(self):\n        \"\"\"List FTDI devices.\"\"\"\n        devs = Ftdi.list_devices('ftdi:///?')\n        self.assertEqual(len(devs), 7)\n        devs = Ftdi.list_devices('ftdi://:232h/?')\n        self.assertEqual(len(devs), 2)\n        devs = Ftdi.list_devices('ftdi://:2232h/?')\n        self.assertEqual(len(devs), 1)\n        devs = Ftdi.list_devices('ftdi://:4232h/?')\n        self.assertEqual(len(devs), 1)\n        devs = Ftdi.list_devices('ftdi://:4232ha/?')\n        self.assertEqual(len(devs), 1)\n        out = StringIO()\n        Ftdi.show_devices('ftdi:///?', out)\n        lines = [ln.strip() for ln in out.getvalue().split('\\n')]\n        lines.pop(0)  # \"Available interfaces\"\n        while lines and not lines[-1]:\n            lines.pop()\n        self.assertEqual(len(lines), 14)\n        portmap = defaultdict(int)\n        reference = {'232': 1, '2232': 2, '4232': 4, '232h': 2, 'ft-x': 1,\n                     '4232ha': 4}\n        for line in lines:\n            url = line.split(' ')[0].strip()\n            parts = urlsplit(url)\n            self.assertEqual(parts.scheme, 'ftdi')\n            self.assertRegex(parts.path, r'^/[1-4]$')\n            product = parts.netloc.split(':')[1]\n            portmap[product] += 1\n        self.assertEqual(portmap, reference)\n\n\nclass MockSimpleDeviceTestCase(FtdiTestCase):\n    \"\"\"Test FTDI APIs with a single-port FTDI device (FT232H)\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft232h.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_enumerate(self):\n        \"\"\"Check simple enumeration of a single FTDI device.\"\"\"\n        ftdi = Ftdi()\n        temp_stdout = StringIO()\n        with redirect_stdout(temp_stdout):\n            self.assertRaises(SystemExit, ftdi.open_from_url, 'ftdi:///?')\n        lines = [ln.strip() for ln in temp_stdout.getvalue().split('\\n')]\n        lines.pop(0)  # \"Available interfaces\"\n        while lines and not lines[-1]:\n            lines.pop()\n        self.assertEqual(len(lines), 1)\n        self.assertTrue(lines[0].startswith('ftdi://'))\n        # skip description, i.e. consider URL only\n        self.assertTrue(lines[0].split(' ')[0].endswith('/1'))\n\n\nclass MockDualDeviceTestCase(FtdiTestCase):\n    \"\"\"Test FTDI APIs with two similar single-port FTDI devices (FT232H)\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft232h_x2.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_enumerate(self):\n        \"\"\"Check simple enumeration of a 2-port FTDI device.\"\"\"\n        ftdi = Ftdi()\n        temp_stdout = StringIO()\n        with redirect_stdout(temp_stdout):\n            self.assertRaises(SystemExit, ftdi.open_from_url, 'ftdi:///?')\n        lines = [ln.strip() for ln in temp_stdout.getvalue().split('\\n')]\n        lines.pop(0)  # \"Available interfaces\"\n        while lines and not lines[-1]:\n            lines.pop()\n        self.assertEqual(len(lines), 2)\n        for line in lines:\n            self.assertTrue(line.startswith('ftdi://'))\n            # skip description, i.e. consider URL only\n            self.assertTrue(line.split(' ')[0].endswith('/1'))\n\n\nclass MockTwoPortDeviceTestCase(FtdiTestCase):\n    \"\"\"Test FTDI APIs with a dual-port FTDI device (FT2232H)\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft2232h.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_enumerate(self):\n        \"\"\"Check simple enumeration of a 4-port FTDI device.\"\"\"\n        ftdi = Ftdi()\n        temp_stdout = StringIO()\n        with redirect_stdout(temp_stdout):\n            self.assertRaises(SystemExit, ftdi.open_from_url, 'ftdi:///?')\n        lines = [ln.strip() for ln in temp_stdout.getvalue().split('\\n')]\n        lines.pop(0)  # \"Available interfaces\"\n        while lines and not lines[-1]:\n            lines.pop()\n        self.assertEqual(len(lines), 2)\n        for pos, line in enumerate(lines, start=1):\n            self.assertTrue(line.startswith('ftdi://'))\n            # skip description, i.e. consider URL only\n            self.assertTrue(line.split(' ')[0].endswith(f'/{pos}'))\n\n\nclass MockFourPortDeviceTestCase(FtdiTestCase):\n    \"\"\"Test FTDI APIs with a quad-port FTDI device (FT4232H, FT4232HA)\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft4232h.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_enumerate(self):\n        \"\"\"Check simple enumeration of two similar FTDI device.\"\"\"\n        ftdi = Ftdi()\n        temp_stdout = StringIO()\n        with redirect_stdout(temp_stdout):\n            self.assertRaises(SystemExit, ftdi.open_from_url, 'ftdi:///?')\n        lines = [ln.strip() for ln in temp_stdout.getvalue().split('\\n')]\n        lines.pop(0)  # \"Available interfaces\"\n        while lines and not lines[-1]:\n            lines.pop()\n        self.assertEqual(len(lines), 4)\n        for pos, line in enumerate(lines, start=1):\n            self.assertTrue(line.startswith('ftdi://'))\n            # skip description, i.e. consider URL only\n            self.assertTrue(line.split(' ')[0].endswith(f'/{pos}'))\n\n\nclass MockManyDevicesTestCase(FtdiTestCase):\n    \"\"\"Test FTDI APIs with several, mixed type FTDI devices\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ftmany.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_enumerate(self):\n        \"\"\"Check simple enumeration of two similar FTDI device.\"\"\"\n        ftdi = Ftdi()\n        temp_stdout = StringIO()\n        with redirect_stdout(temp_stdout):\n            self.assertRaises(SystemExit, ftdi.open_from_url, 'ftdi:///?')\n        lines = [ln.strip() for ln in temp_stdout.getvalue().split('\\n')]\n        lines.pop(0)  # \"Available interfaces\"\n        while lines and not lines[-1]:\n            lines.pop()\n        self.assertEqual(len(lines), 14)\n        for line in lines:\n            self.assertTrue(line.startswith('ftdi://'))\n            # skip description, i.e. consider URL only\n            url = line.split(' ')[0]\n            urlparts = urlsplit(url)\n            self.assertEqual(urlparts.scheme, 'ftdi')\n            parts = urlparts.netloc.split(':')\n            if (parts[1] == '4232') or (parts[1] == '4232ha'):\n                # def file contains no serial number, so expect bus:addr syntax\n                self.assertEqual(len(parts), 4)\n                self.assertRegex(parts[2], r'^\\d$')\n                self.assertRegex(parts[3], r'^\\d$')\n            else:\n                # other devices are assigned a serial number\n                self.assertEqual(len(parts), 3)\n                self.assertTrue(parts[2].startswith('FT'))\n            self.assertRegex(urlparts.path, r'^/\\d$')\n\n\nclass MockSimpleDirectTestCase(FtdiTestCase):\n    \"\"\"Test FTDI open/close APIs with a basic featured FTDI device (FT230H)\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft230x.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_open_close(self):\n        \"\"\"Check simple open/close sequence.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        self.assertEqual(ftdi.usb_path, (1, 1, 0))\n        ftdi.close()\n\n    def test_open_bitbang(self):\n        \"\"\"Check simple open/close BitBang sequence.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_bitbang_from_url('ftdi:///1')\n        ftdi.close()\n\n    def test_open_mpsse(self):\n        \"\"\"Check simple MPSSE access.\"\"\"\n        ftdi = Ftdi()\n        # FT230X is a pure UART bridge, MPSSE should not be available\n        self.assertRaises(FtdiMpsseError,\n                          ftdi.open_mpsse_from_url, 'ftdi:///1')\n\n\nclass MockSimpleMpsseTestCase(FtdiTestCase):\n    \"\"\"Test FTDI open/close APIs with a MPSSE featured FTDI device (FT232H)\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft232h.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_open_close(self):\n        \"\"\"Check simple open/close sequence.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        self.assertEqual(ftdi.usb_path, (4, 5, 0))\n        ftdi.close()\n\n    def test_open_bitbang(self):\n        \"\"\"Check simple open/close BitBang sequence.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_bitbang_from_url('ftdi:///1')\n        ftdi.close()\n\n    def test_open_mpsse(self):\n        \"\"\"Check simple MPSSE access.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_mpsse_from_url('ftdi:///1')\n        ftdi.close()\n\n\nclass MockSimpleGpioTestCase(FtdiTestCase):\n    \"\"\"Test FTDI GPIO APIs\n    \"\"\"\n\n    def tearDown(self):\n        self.loader.unload()\n\n    def _test_gpio(self):\n        \"\"\"Check simple GPIO write and read sequence.\"\"\"\n        with open('pyftdi/tests/resources/ft232h.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        gpio = GpioController()\n        # access to the virtual GPIO port\n        out_pins = 0xAA\n        gpio.configure('ftdi://:232h/1', direction=out_pins)\n        bus, address, iface = gpio.ftdi.usb_path\n        self.assertEqual((bus, address, iface), (4, 5, 0))\n        vftdi = self.loader.get_virtual_ftdi(bus, address)\n        vport = vftdi.get_port(1)\n        gpio.write_port(0xF3)\n        self.assertEqual(vport.gpio, 0xAA & 0xF3)\n        vport.gpio = 0x0c\n        vio = gpio.read_port()\n        self.assertEqual(vio, (0xAA & 0xF3) | (~0xAA & 0x0c))\n        gpio.close()\n\n    def test_baudrate(self):\n        \"\"\"Check simple GPIO write and read sequence.\"\"\"\n        # load custom CBUS config, with:\n        # - CBUS0: GPIO (gpio)\n        # - CBUS1: GPIO (gpio)\n        # - CBUS0: DRIVE1 (forced to high level)\n        # - CBUS0: TXLED  (eq. to highz for tests)\n        with open('pyftdi/tests/resources/ft230x_io.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        gpio = GpioController()\n        # access to the virtual GPIO port\n        out_pins = 0xAA\n        gpio.configure('ftdi://:230x/1', direction=out_pins)\n        vftdi = self.loader.get_virtual_ftdi(1, 1)\n        vftdi.get_port(1)\n        baudrate = 1000000\n        gpio.set_frequency(baudrate)\n        gpio.close()\n\n\nclass MockSimpleUartTestCase(FtdiTestCase):\n    \"\"\"Test FTDI UART APIs\n    \"\"\"\n\n    def tearDown(self):\n        self.loader.unload()\n\n    def test_uart(self):\n        \"\"\"Check simple TX/RX sequence.\"\"\"\n        with open('pyftdi/tests/resources/ft232h.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        port = serial_for_url('ftdi:///1')\n        bus, address, _ = port.usb_path\n        vftdi = self.loader.get_virtual_ftdi(bus, address)\n        vport = vftdi.get_port(1)\n        msg = ascii_letters\n        port.write(msg.encode())\n        txd = vport[vport.UART_PINS.TXD]\n        buf = txd.read(len(ascii_letters)+10).decode()\n        self.assertEqual(msg, buf)\n        msg = ''.join(reversed(msg))\n        rxd = vport[vport.UART_PINS.TXD]\n        rxd.write(msg.encode())\n        buf = port.read(len(ascii_letters)).decode()\n        self.assertEqual(msg, buf)\n        port.close()\n\n    def test_uart_loopback(self):\n        \"\"\"Check TXD/RXD loopback.\"\"\"\n        with open('pyftdi/tests/resources/ft232h.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        port = serial_for_url('ftdi:///1')\n        bus, address, _ = port.usb_path\n        vftdi = self.loader.get_virtual_ftdi(bus, address)\n        vport = vftdi.get_port(1)\n        txd = vport[vport.UART_PINS.TXD]\n        rxd = vport[vport.UART_PINS.RXD]\n        txd.connect_to(rxd)\n        msg = ascii_letters\n        port.write(msg.encode())\n        buf = port.read(len(ascii_letters)).decode()\n        self.assertEqual(msg, buf)\n        port.close()\n\n    def test_baudrate_fs_dev(self):\n        \"\"\"Check baudrate settings for full speed devices.\"\"\"\n        with open('pyftdi/tests/resources/ft230x.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        port = serial_for_url('ftdi:///1', baudrate=1200)\n        bus, address, _ = port.usb_path\n        vftdi = self.loader.get_virtual_ftdi(bus, address)\n        vport = vftdi.get_port(1)\n        self.assertRaises(ValueError, setattr, port, 'baudrate', 100)\n        self.assertRaises(ValueError, setattr, port, 'baudrate', 3100000)\n        for baudrate in (200, 600, 1200, 2400, 4800, 9600, 115200, 230400,\n                         460800, 490000, 921600, 1000000, 1200000, 1500000,\n                         2000000, 3000000):\n            port.baudrate = baudrate\n            self.assertEqual(port.ftdi.baudrate, vport.baudrate)\n        port.close()\n\n    def test_baudrate_hs_dev(self):\n        \"\"\"Check baudrate settings for high speed devices.\"\"\"\n        with open('pyftdi/tests/resources/ft232h.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        port = serial_for_url('ftdi:///1', baudrate=1200)\n        bus, address, _ = port.usb_path\n        vftdi = self.loader.get_virtual_ftdi(bus, address)\n        vport = vftdi.get_port(1)\n        self.assertRaises(ValueError, setattr, port, 'baudrate', 100)\n        self.assertRaises(ValueError, setattr, port, 'baudrate', 12100000)\n        for baudrate in (200, 600, 1200, 2400, 4800, 9600, 115200, 230400,\n                         460800, 490000, 921600, 1000000, 1200000, 1500000,\n                         2000000, 3000000, 4000000, 6000000):\n            port.baudrate = baudrate\n            self.assertEqual(port.ftdi.baudrate, vport.baudrate)\n        port.close()\n\n\nclass MockRawExtEepromTestCase(FtdiTestCase):\n    \"\"\"Test FTDI EEPROM low-level APIs with external EEPROM device\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft232h.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def _restore_eeprom(self, ftdi):\n        bus, address, _ = ftdi.usb_path\n        vftdi = self.loader.get_virtual_ftdi(bus, address)\n        data = self.loader.eeprom_backup\n        size = len(vftdi.eeprom)\n        if len(data) < size:\n            data = bytearray(data) + bytes(size-len(data))\n        vftdi.eeprom = bytes(data)\n\n    def test_dump(self):\n        \"\"\"Check EEPROM full content.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        self._restore_eeprom(ftdi)\n        ref_data = bytes(list(range(256)))\n        size = len(ref_data)\n        data = ftdi.read_eeprom()\n        self.assertEqual(len(data), size)\n        self.assertEqual(ref_data, data)\n        ftdi.close()\n\n    def test_random_access_read(self):\n        \"\"\"Check EEPROM random read access.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        self._restore_eeprom(ftdi)\n        ref_data = bytes(list(range(256)))\n        size = len(ref_data)\n        # out of bound\n        self.assertRaises(ValueError, ftdi.read_eeprom, size, 2)\n        # last bytes\n        buf = ftdi.read_eeprom(size-2, 2)\n        self.assertEqual(buf[0:2], ref_data[-2:])\n        self.assertEqual(buf[0:2], b'\\xfe\\xff')\n        # out of bound\n        self.assertRaises(ValueError, ftdi.read_eeprom, size-2, 4)\n        # unaligned access\n        buf = ftdi.read_eeprom(1, 2)\n        self.assertEqual(buf[0:2], ref_data[1:3])\n        self.assertEqual(buf[0:2], b'\\x01\\x02')\n        # long read, unaligned access, unaligned size\n        buf = ftdi.read_eeprom(43, 43)\n        self.assertEqual(len(buf), 43)\n        self.assertEqual(buf, ref_data[43:86])\n        ftdi.close()\n\n    def test_randow_access_write(self):\n        \"\"\"Check EEPROM random write access.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        bus, address, _ = ftdi.usb_path\n        vftdi = self.loader.get_virtual_ftdi(bus, address)\n        self._restore_eeprom(ftdi)\n        checksum1 = vftdi.eeprom[-2:]\n        orig_data = vftdi.eeprom[:8]\n        ref_data = b'ABCD'\n        ftdi.write_eeprom(0, ref_data, dry_run=False)\n        checksum2 = vftdi.eeprom[-2:]\n        # verify the data have been written\n        self.assertEqual(vftdi.eeprom[:4], ref_data)\n        # verify the data have not been overwritten\n        self.assertEqual(vftdi.eeprom[4:8], orig_data[4:])\n        # verify the checksum has been updated\n        # TODO compute the expected checksum\n        self.assertNotEqual(checksum1, checksum2)\n        checksum1 = vftdi.eeprom[-2:]\n        orig_data = vftdi.eeprom[:24]\n        ftdi.write_eeprom(9, ref_data, dry_run=False)\n        checksum2 = vftdi.eeprom[-2:]\n        # verify the unaligned data have been written\n        self.assertEqual(vftdi.eeprom[9:13], ref_data)\n        # verify the data have not been overwritten\n        self.assertEqual(vftdi.eeprom[:9], orig_data[:9])\n        self.assertEqual(vftdi.eeprom[13:24], orig_data[13:])\n        # verify the checksum has been updated\n        self.assertNotEqual(checksum1, checksum2)\n        checksum1 = vftdi.eeprom[-2:]\n        orig_data = vftdi.eeprom[:48]\n        ftdi.write_eeprom(33, ref_data[:3], dry_run=False)\n        checksum2 = vftdi.eeprom[-2:]\n        # verify the unaligned data have been written\n        self.assertEqual(vftdi.eeprom[33:36], ref_data[:3])\n        # verify the data have not been overwritten\n        self.assertEqual(vftdi.eeprom[:33], orig_data[:33])\n        self.assertEqual(vftdi.eeprom[36:48], orig_data[36:])\n        # verify the checksum has been updated\n        self.assertNotEqual(checksum1, checksum2)\n\n\nclass MockRawIntEepromTestCase(FtdiTestCase):\n    \"\"\"Test FTDI EEPROM low-level APIs with internal EEPROM device\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        with open('pyftdi/tests/resources/ft230x.yaml', 'rb') as yfp:\n            cls.loader.load(yfp)\n\n    def test_descriptor_update(self):\n        \"\"\"Check EEPROM content overrides YaML configuration.\"\"\"\n        # this test is more about testing the virtual FTDI infrastructure\n        # than a pure PyFtdi test\n        devs = Ftdi.list_devices('ftdi:///?')\n        self.assertEqual(len(devs), 1)\n        desc = devs[0][0]\n        # these values are not the ones defined in YaML, but stored in EEPROM\n        self.assertEqual(desc.sn, 'FT3KMGTL')\n        self.assertEqual(desc.description, 'LC231X')\n\n    def test_eeprom_read(self):\n        \"\"\"Check full read sequence.\"\"\"\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        data = ftdi.read_eeprom()\n        self.assertEqual(len(data), 0x400)\n        ftdi.close()\n\n\nclass MockCBusEepromTestCase(FtdiTestCase):\n    \"\"\"Test FTDI EEPROM APIs that manage CBUS feature\n    \"\"\"\n\n    def tearDown(self):\n        self.loader.unload()\n\n    def test_ft230x(self):\n        with open('pyftdi/tests/resources/ft230x.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        eeprom = FtdiEeprom()\n        eeprom.open('ftdi:///1')\n        # default EEPROM config does not have any CBUS configured as GPIO\n        self.assertEqual(eeprom.cbus_pins, [])\n        self.assertEqual(eeprom.cbus_mask, 0)\n        # enable CBUS1 and CBUS3 as GPIO\n        eeprom.set_property('cbus_func_1', 'gpio')\n        eeprom.set_property('cbus_func_3', 'gpio')\n        eeprom.sync()\n        self.assertEqual(eeprom.cbus_pins, [1, 3])\n        self.assertEqual(eeprom.cbus_mask, 0xA)\n        # enable CBUS0 and CBUS2 as GPIO\n        eeprom.set_property('cbus_func_0', 'gpio')\n        eeprom.set_property('cbus_func_2', 'gpio')\n        # not yet committed\n        self.assertEqual(eeprom.cbus_pins, [1, 3])\n        self.assertEqual(eeprom.cbus_mask, 0xA)\n        eeprom.sync()\n        # committed\n        self.assertEqual(eeprom.cbus_pins, [0, 1, 2, 3])\n        self.assertEqual(eeprom.cbus_mask, 0xF)\n        # invalid CBUS pin\n        self.assertRaises(ValueError, eeprom.set_property,\n                          'cbus_func_4', 'gpio')\n        # invalid pin function\n        self.assertRaises(ValueError, eeprom.set_property,\n                          'cbus_func_0', 'gpio_')\n        # invalid pin\n        self.assertRaises(ValueError, eeprom.set_property,\n                          'cbus_func', 'gpio')\n        # valid alternative mode\n        eeprom.set_property('cbus_func_0', 'txled')\n        eeprom.set_property('cbus_func_1', 'rxled')\n        eeprom.sync()\n        self.assertEqual(eeprom.cbus_pins, [2, 3])\n        self.assertEqual(eeprom.cbus_mask, 0xC)\n        eeprom.close()\n\n    def test_ft232h(self):\n        with open('pyftdi/tests/resources/ft232h_x2.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        eeprom = FtdiEeprom()\n        eeprom.open('ftdi://::FT1ABC1/1', ignore=True)\n        eeprom.erase()\n        eeprom.initialize()\n        # default EEPROM config does not have any CBUS configured as GPIO\n        self.assertEqual(eeprom.cbus_pins, [])\n        self.assertEqual(eeprom.cbus_mask, 0)\n        eeprom.set_property('cbus_func_6', 'gpio')\n        eeprom.set_property('cbus_func_9', 'gpio')\n        # not yet committed\n        self.assertEqual(eeprom.cbus_pins, [])\n        self.assertEqual(eeprom.cbus_mask, 0)\n        eeprom.sync()\n        # committed\n        self.assertEqual(eeprom.cbus_pins, [6, 9])\n        self.assertEqual(eeprom.cbus_mask, 0xA)\n        eeprom.set_property('cbus_func_5', 'gpio')\n        eeprom.set_property('cbus_func_8', 'gpio')\n        eeprom.sync()\n        self.assertEqual(eeprom.cbus_pins, [5, 6, 8, 9])\n        self.assertEqual(eeprom.cbus_mask, 0xF)\n        # pin1 and pin3 is not configurable as GPIO\n        self.assertRaises(ValueError, eeprom.set_property,\n                          'cbus_func_1', 'gpio')\n        self.assertRaises(ValueError, eeprom.set_property,\n                          'cbus_func_3', 'gpio')\n        eeprom.close()\n\n\nclass MockCbusGpioTestCase(FtdiTestCase):\n    \"\"\"Test FTDI CBUS GPIO APIs\n    \"\"\"\n\n    def tearDown(self):\n        self.loader.unload()\n\n    def test_230x(self):\n        \"\"\"Check simple GPIO write and read sequence.\"\"\"\n        # load custom CBUS config, with:\n        # - CBUS0: GPIO (gpio)\n        # - CBUS1: GPIO (gpio)\n        # - CBUS0: DRIVE1 (forced to high level)\n        # - CBUS0: TXLED  (eq. to highz for tests)\n        with open('pyftdi/tests/resources/ft230x_io.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        self.assertEqual(ftdi.has_cbus, True)\n        vftdi = self.loader.get_virtual_ftdi(1, 1)\n        vport = vftdi.get_port(1)\n        # CBUS0: in, CBUS1: out, CBUS2: in, CBUS3: out\n        #   however, only CBUS0 and CBUS1 are mapped as GPIO,\n        #   CBUS2 forced to 1 and CBUS3 not usable as IO\n        #   even if use mask is 1111\n        eeprom_mask = 0b0011\n        eeprom_force = 0b0100\n        cbus_mask = 0b1111\n        cbus_dir = 0b1010\n        ftdi.set_cbus_direction(cbus_mask, cbus_dir)\n        cbus_out = 0b0011\n        # CBUS0: 1, CBUS1: 1\n        #   however, only CBUS1 is out, so CBUS0 output value should be ignored\n        ftdi.set_cbus_gpio(cbus_out)\n        exp_out = cbus_dir & cbus_out\n        exp_out &= eeprom_mask\n        exp_out |= eeprom_force\n        vcbus, vactive = vport.cbus\n        self.assertEqual(vcbus, exp_out)\n        self.assertEqual(vactive, eeprom_mask | eeprom_force)\n        cbus_out = 0b0000\n        ftdi.set_cbus_gpio(cbus_out)\n        exp_out = cbus_dir & cbus_out\n        exp_out &= eeprom_mask\n        exp_out |= eeprom_force\n        vcbus, vactive = vport.cbus\n        self.assertEqual(vcbus, exp_out)\n        cbus_in = 0b0101\n        vport.cbus = cbus_in\n        cbus = ftdi.get_cbus_gpio()\n        exp_in = cbus_in & eeprom_mask\n        self.assertEqual(cbus, exp_in)\n        ftdi.close()\n\n    def test_lc231x(self):\n        \"\"\"Check simple GPIO write and read sequence.\"\"\"\n        # load custom CBUS config, with:\n        # - CBUS0: GPIO (gpio)\n        # - CBUS1: TXLED\n        # - CBUS2: DRIVE0 (to light up RX green led)\n        # - CBUS3: GPIO (gpio)\n        #   only CBUS0 and CBUS3 are available on LC231X\n        # - CBUS1 is connected to TX led, CBUS2 to RX led\n        with open('pyftdi/tests/resources/ft231x_cbus.yaml', 'rb') as yfp:\n            self.loader.load(yfp)\n        ftdi = Ftdi()\n        ftdi.open_from_url('ftdi:///1')\n        self.assertEqual(ftdi.has_cbus, True)\n        vftdi = self.loader.get_virtual_ftdi(1, 1)\n        vport = vftdi.get_port(1)\n        # CBUS0: in, CBUS1: out, CBUS2: in, CBUS3: out\n        #   however, only CBUS0 and CBUS3 are mapped as GPIO,\n        #   CBUS1 not usable as IO, CBUS2 is fixed to low\n        #   even if use mask is 1111\n        eeprom_mask = 0b1001\n        eeprom_force_low = 0b0100\n        cbus_mask = 0b1111\n        cbus_dir = 0b1010\n        ftdi.set_cbus_direction(cbus_mask, cbus_dir)\n        cbus_out = 0b1111\n        # however, only CBUS0 & 3 are out, so CBUS1/CBUS2 should be ignored\n        ftdi.set_cbus_gpio(cbus_out)\n        exp_out = cbus_dir & cbus_out\n        exp_out &= eeprom_mask\n        vcbus, vactive = vport.cbus\n        self.assertEqual(vcbus, exp_out)\n        self.assertEqual(vactive, eeprom_mask | eeprom_force_low)\n        cbus_out = 0b0000\n        ftdi.set_cbus_gpio(cbus_out)\n        exp_out = cbus_dir & cbus_out\n        exp_out &= eeprom_mask\n        vcbus, vactive = vport.cbus\n        self.assertEqual(vcbus, exp_out)\n        cbus_in = 0b0101\n        vport.cbus = cbus_in\n        cbus = ftdi.get_cbus_gpio()\n        exp_in = cbus_in & eeprom_mask\n        self.assertEqual(cbus, exp_in)\n        ftdi.close()\n\n\ndef suite():\n    suite_ = TestSuite()\n    suite_.addTest(TestLoader().loadTestsFromModule(modules[__name__]))\n    return suite_\n\n\ndef setup_module():\n    testmod(modules[__name__])\n    debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n    if debug:\n        formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)-7s'\n                                      ' %(name)-18s [%(lineno)4d] %(message)s',\n                                      '%H:%M:%S')\n    else:\n        formatter = logging.Formatter('%(message)s')\n    level = environ.get('FTDI_LOGLEVEL', 'warning').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    FtdiLogger.set_level(loglevel)\n    FtdiLogger.set_formatter(formatter)\n    # Force PyUSB to use PyFtdi test framework for USB backends\n    UsbTools.BACKENDS = ('backend.usbvirt', )\n    # Ensure the virtual backend can be found and is loaded\n    backend = UsbTools.find_backend()\n    try:\n        # obtain the loader class associated with the virtual backend\n        global MockLoader\n        MockLoader = backend.create_loader()\n    except AttributeError as exc:\n        raise AssertionError('Cannot load virtual USB backend') from exc\n\n\ndef main():\n    setup_module()\n    ut_main(defaultTest='suite')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/tests/resources/custom_vidpid.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x8a99\n      version: 0x700\n      manufacturer: FTDI\n      product: TUMPA\n      serialnumber: TIAO_USB_1\n  - bus: 1\n    address: 2\n    descriptor:\n      vid: 0xcafe\n      pid: 0xbeef\n      version: 0x1000\n      manufacturer: Acme\n      product: Whatever\n      serialnumber: SomeRandomNumber\n"
  },
  {
    "path": "pyftdi/tests/resources/ft2232h.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6010\n      version: 0x700\n      manufacturer: FTDI\n      product: FT2232H\n      serialnumber: FT2DEF\n    configurations:\n      - interfaces:\n        - repeat: 2\n\n"
  },
  {
    "path": "pyftdi/tests/resources/ft230x.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6015\n      version: 0x1000\n      manufacturer: FTDI\n      product: FT230X\n      serialnumber: FT1XYZ\n    eeprom:\n      load: yes\n      data: |  # hex byte encode format\n        80 00 03 04 15 60 00 10 80 2d 08 00 00 00 a0 0a\n        aa 0e b8 12 00 00 00 00 00 00 00 02 01 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        29 36 d6 c9 01 00 77 f2 b8 68 40 00 00 00 00 00\n        00 00 00 00 44 4d 51 38 4c 42 4a 45 00 00 00 00\n        0a 03 46 00 54 00 44 00 49 00 0e 03 4c 00 43 00\n        32 00 33 00 31 00 58 00 12 03 46 00 54 00 33 00\n        4b 00 4d 00 47 00 54 00 4c 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 cc 57\n"
  },
  {
    "path": "pyftdi/tests/resources/ft230x_io.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6015\n      version: 0x1000\n      manufacturer: FTDI\n      product: FT230X\n      serialnumber: FT1XYZ\n    eeprom:\n      load: yes\n      data: |  # hex byte encode format\n         80 00 03 04 15 60 00 10 80 2d 08 00 00 00 a0 0a\n         aa 0e b8 0c 00 00 00 00 00 00 08 08 07 01 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         29 36 d6 c9 01 00 77 f2 b8 68 40 00 00 00 00 00\n         00 00 00 00 44 4d 51 38 4c 42 4a 45 00 00 00 00\n         0a 03 46 00 54 00 44 00 49 00 0e 03 4c 00 43 00\n         32 00 33 00 31 00 58 00 0c 03 41 00 42 00 43 00\n         44 00 45 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 d8 bf\n        # CBUS0: GPIO (gpio)\n        # CBUS1: GPIO (gpio)\n        # CBUS2: DRIVE1 (forced to high level)\n        # CBUS3: TXLED  (eq. to highz for tests)"
  },
  {
    "path": "pyftdi/tests/resources/ft231x_cbus.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6015\n      version: 0x1000\n      manufacturer: FTDI\n      product: LC231X\n      serialnumber: FT1RANDOM\n    eeprom:\n      load: yes\n      data: |  # hex byte encode format\n         80 00 03 04 15 60 00 10 80 2d 08 00 00 00 a0 0a\n         aa 0e b8 0c 00 00 00 00 00 00 08 01 06 08 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         29 36 d6 c9 01 00 77 f2 b8 68 40 00 00 00 00 00\n         00 00 00 00 44 4d 51 38 4c 42 4a 45 00 00 00 00\n         0a 03 46 00 54 00 44 00 49 00 0e 03 4c 00 43 00\n         32 00 33 00 31 00 58 00 0c 03 41 00 42 00 43 00\n         44 00 45 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n         00 00 00 00 00 00 00 00 00 00 00 00 00 00 d0 67\n        # CBUS0: GPIO (gpio)\n        # CBUS1: TXLED\n        # CBUS2: DRIVE0 (to light up RX green led)\n        # CBUS3: GPIO (gpio)\n        # only CBUS0 and CBUS3 are available on LC231X\n        # CBUS1 is connected to TX led, CBUS2 to RX led"
  },
  {
    "path": "pyftdi/tests/resources/ft232h.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\n# This file defines a regular FT232H virtual device\n# It also shows supported options and device definition structure\n# In most use cases, far simpler device definitions may be used, as the\n# MockLoader automatically generates sub device structures that are not\n# expliclty defined.\n\ndevices:\n  - bus: 4\n    address: 5\n    speed: high\n    noaccess: no\n    descriptor:\n      usb: 0x200\n      class: 0\n      subclass: 0\n      protocol: 0\n      maxpacketsize: 8\n      vid: 0x403\n      pid: 0x6014\n      version: 0x900\n      manufacturer: FTDI\n      product: FT232H\n      serialnumber: FT1ABC\n    configurations:\n      - descriptor:\n          attributes:\n            - selfpowered\n          maxpower: 150\n        interfaces:\n          - alternatives:\n              - descriptor:\n                  class: 0xff\n                  subclass: 0xff\n                  protocol: 0xff\n                endpoints:\n                  - descriptor:\n                      number: 1\n                      maxpacketsize: 64\n                      interval: 0\n                      direction: in\n                      type: bulk\n                  - descriptor:\n                      number: 2\n                      maxpacketsize: 64\n                      interval: 0\n                      direction: out\n                      type: bulk\n    eeprom:\n      load: no\n      model: 93c66\n      # invalid EEPROM content to perform content access tests\n      data: |  # hex byte encode format\n        00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n        10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f\n        20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f\n        30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f\n        40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f\n        50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f\n        60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f\n        70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f\n        80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f\n        90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f\n        a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af\n        b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf\n        c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf\n        d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df\n        e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef\n        f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff\n      # data: !!binary |  # base64 encode format\n      #  R0lGODlhDAAMAIQAAP//9/X\n      #  17unp5WZmZgAAAOfn515eXv\n      #  Pz7Y6OjuDg4J+fn5OTk6enp\n      #  56enmleECcgggoBADs=\n"
  },
  {
    "path": "pyftdi/tests/resources/ft232h_x2.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6014\n      version: 0x900\n      manufacturer: FTDI\n      product: FT232H\n      serialnumber: FT1ABC1\n  - bus: 1\n    address: 2\n    descriptor:\n      vid: 0x403\n      pid: 0x6014\n      version: 0x900\n      manufacturer: FTDI\n      product: FT232H\n      serialnumber: FT1ABC2\n"
  },
  {
    "path": "pyftdi/tests/resources/ft232r.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6015\n      version: 0x0600\n      manufacturer: FTDI\n      product: Basic UART\n      serialnumber: FT2XYZ\n    eeprom:\n      load: yes\n      data: |  # hex byte encode format\n        00 40 03 04 01 60 00 06 a0 32 08 00 00 02 98 0a\n        a2 2c ce 12 32 11 05 00 0a 03 46 00 54 00 44 00\n        49 00 2c 03 55 00 4d 00 32 00 33 00 32 00 52 00\n        20 00 55 00 53 00 42 00 20 00 3c 00 2d 00 3e 00\n        20 00 53 00 65 00 72 00 69 00 61 00 6c 00 12 03\n        46 00 54 00 47 00 58 00 53 00 59 00 57 00 4a 00\n        02 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n        00 00 00 00 00 00 00 00 00 00 00 00 00 00 09 13\n"
  },
  {
    "path": "pyftdi/tests/resources/ft4232h.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6011\n      version: 0x800\n      manufacturer: FTDI\n      product: FT4232H\n      serialnumber: FT3GHI\n    configurations:\n      - interfaces:\n        - repeat: 4\n\n"
  },
  {
    "path": "pyftdi/tests/resources/ft4232ha.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6048\n      version: 0x3600\n      manufacturer: FTDI\n      product: FT4232HA\n      serialnumber: FT7E0S92\n    configurations:\n      - interfaces:\n        - repeat: 4\n\n"
  },
  {
    "path": "pyftdi/tests/resources/ftmany.yaml",
    "content": "# SPDX-License-Identifier: BSD-3-Clause\n\ndevices:\n  - bus: 1\n    address: 2\n    descriptor:\n      vid: 0x403\n      pid: 0x6015\n      version: 0x1000\n      manufacturer: FTDI\n      product: FT230X\n      serialnumber: FT1XYZ\n  - bus: 2\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6014\n      version: 0x900\n      manufacturer: FTDI\n      product: FT232H\n      serialnumber: FT1ABC1\n  - bus: 2\n    address: 2\n    descriptor:\n      vid: 0x403\n      pid: 0x6014\n      version: 0x900\n      manufacturer: FTDI\n      product: FT232H\n      serialnumber: FT1ABC2\n  - bus: 3\n    address: 1\n    descriptor:\n      vid: 0x403\n      pid: 0x6010\n      version: 0x700\n      manufacturer: FTDI\n      product: FT2232H\n      serialnumber: FT2DEF\n    configurations:\n      - interfaces:\n        - repeat: 2\n  - bus: 3\n    address: 3\n    descriptor:\n      vid: 0x403\n      pid: 0x6011\n      version: 0x800\n      manufacturer: FTDI\n      product: FT4232H\n    configurations:\n      - interfaces:\n        - repeat: 4\n  - bus: 3\n    address: 4\n    descriptor:\n      vid: 0x403\n      pid: 0x6001\n      version: 0x600\n      manufacturer: FTDI\n      product: FT2232R\n      serialnumber: FT1OPQ\n  - bus: 3\n    address: 5\n    descriptor:\n      vid: 0x403\n      pid: 0x6048\n      version: 0x3600\n      manufacturer: FTDI\n      product: FT4232HA\n    configurations:\n      - interfaces:\n        - repeat: 4\n\n\n\n\n"
  },
  {
    "path": "pyftdi/tests/spi.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright (c) 2017-2020, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=empty-docstring\n# pylint: disable=missing-docstring\n\nimport logging\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\nfrom binascii import hexlify\nfrom doctest import testmod\nfrom os import environ\nfrom sys import modules, stderr, stdout\nfrom time import sleep\nfrom pyftdi import FtdiLogger\nfrom pyftdi.misc import to_bool\nfrom pyftdi.spi import SpiController, SpiIOError\n\n\nclass SpiDataFlashTest:\n    \"\"\"Basic test for a MX25L1606E data flash device selected as CS0,\n       SPI mode 0\n    \"\"\"\n\n    def __init__(self):\n        self._spi = SpiController(cs_count=3)\n\n    def open(self):\n        \"\"\"Open an SPI connection to a slave\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n        self._spi.configure(url, debug=debug)\n\n    def read_jedec_id(self):\n        port = self._spi.get_port(0, freq=3E6, mode=0)\n        jedec_id = port.exchange([0x9f], 3)\n        hex_jedec_id = hexlify(jedec_id).decode()\n        print('JEDEC ID:', hex_jedec_id)\n        return hex_jedec_id\n\n    def close(self):\n        \"\"\"Close the SPI connection\"\"\"\n        self._spi.terminate()\n\n\nclass SpiAccelTest:\n    \"\"\"Basic test for an ADXL345 device selected as CS1,\n       SPI mode 3\n    \"\"\"\n\n    def __init__(self):\n        self._spi = SpiController(cs_count=3)\n\n    def open(self):\n        \"\"\"Open an SPI connection to a slave\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n        self._spi.configure(url, debug=debug)\n\n    def read_device_id(self):\n        port = self._spi.get_port(1, freq=6E6, mode=3)\n        device_id = port.exchange([0x00], 1)\n        hex_device_id = hexlify(device_id).decode()\n        print('DEVICE ID:', hex_device_id)\n        return hex_device_id\n\n    def close(self):\n        \"\"\"Close the SPI connection\"\"\"\n        self._spi.terminate()\n\n\nclass SpiRfda2125Test:\n    \"\"\"Basic test for a RFDA2125 Digital Controlled Variable Gain Amplifier\n       selected as CS2,\n       SPI mode 0\n    \"\"\"\n\n    def __init__(self):\n        self._spi = SpiController(cs_count=3)\n        self._port = None\n\n    def open(self):\n        \"\"\"Open an SPI connection to a slave\"\"\"\n        url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n        self._spi.configure(url, debug=debug)\n        self._port = self._spi.get_port(2, freq=1E6, mode=0)\n\n    def change_attenuation(self, value):\n        if not 0.0 <= value <= 31.5:\n            print('Out-of-bound attenuation', file=stderr)\n        intval = 63-int(value*2)\n        self._port.write(bytes([intval]), 1)\n\n    def close(self):\n        \"\"\"Close the SPI connection\"\"\"\n        self._spi.terminate()\n\n\nclass SpiTestCase(TestCase):\n    \"\"\"FTDI SPI driver test case\n\n       Simple test to demonstrate SPI feature.\n\n       Please ensure that the HW you connect to the FTDI port A does match\n       the encoded configuration. GPIOs can be driven high or low, so check\n       your HW setup before running this test as it might damage your HW.\n\n       Do NOT run this test if you use FTDI port A as an UART or I2C\n       bridge -or any unsupported setup!! You've been warned.\n    \"\"\"\n\n    def test_spi1(self):\n        spi = SpiDataFlashTest()\n        spi.open()\n        jedec_id = spi.read_jedec_id()\n        self.assertIn(jedec_id, ('c22016', 'bf254a'))\n        spi.close()\n\n    def _test_spi2(self):\n        spi = SpiAccelTest()\n        spi.open()\n        device_id = spi.read_device_id()\n        self.assertEqual(device_id, 'e5')\n        spi.close()\n\n    def _test_spi3(self):\n        spi = SpiRfda2125Test()\n        spi.open()\n        slope = 1\n        attenuation = 0.0\n        for _ in range(10):\n            for _ in range(63):\n                attenuation += float(slope)\n                print(attenuation/2.0)\n                spi.change_attenuation(attenuation/2.0)\n                sleep(0.05)  # 50 ms\n            slope = -slope\n        spi.close()\n\n\nclass SpiGpioTestCase(TestCase):\n    \"\"\"Basic test for GPIO access w/ SPI mode\n\n       It expects the following I/O setup:\n\n       AD4 connected t0 AC0\n       AD5 connected t0 AC1\n       AD6 connected t0 AC2\n       AD7 connected t0 AC3\n    \"\"\"\n\n    # AD0: SCLK, AD1: MOSI, AD2: MISO, AD3: /CS\n    AD_OFFSET = 4\n    AC_OFFSET = 8\n    PIN_COUNT = 4\n\n    @classmethod\n    def setUpClass(cls):\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n\n    def setUp(self):\n        self._spi = SpiController(cs_count=1)\n        self._spi.configure(self.url, debug=self.debug)\n        self._port = self._spi.get_port(0, freq=1E6, mode=0)\n        self._io = self._spi.get_gpio()\n\n    def tearDown(self):\n        \"\"\"Close the SPI connection\"\"\"\n        self._spi.terminate()\n\n    def test_ac_to_ad(self):\n        ad_pins = ((1 << self.PIN_COUNT) - 1) << self.AD_OFFSET  # input\n        ac_pins = ((1 << self.PIN_COUNT) - 1) << self.AC_OFFSET  # output\n        io_pins = ad_pins | ac_pins\n\n        def ac_to_ad(ac_output):\n            ac_output &= ac_pins\n            ac_output >>= self.AC_OFFSET - self.AD_OFFSET\n            return ac_output & ad_pins\n\n        self._io.set_direction(io_pins, ac_pins)\n        for ac_pin in range(1 << self.PIN_COUNT):\n            ac_out = ac_pin << self.AC_OFFSET\n            ad_in = ac_to_ad(ac_out)\n            self._io.write(ac_out)\n            # random SPI exchange to ensure SPI does not change GPIO\n            self._port.exchange([0x00, 0xff], 2)\n            buf = self._io.read()\n            self.assertEqual(buf, ad_in)\n        self.assertRaises(SpiIOError, self._io.write, ad_pins)\n\n    def test_ad_to_ac(self):\n        ad_pins = ((1 << self.PIN_COUNT) - 1) << self.AD_OFFSET  # output\n        ac_pins = ((1 << self.PIN_COUNT) - 1) << self.AC_OFFSET  # input\n        io_pins = ad_pins | ac_pins\n\n        def ad_to_ac(ad_output):\n            ad_output &= ad_pins\n            ad_output <<= self.AC_OFFSET - self.AD_OFFSET\n            return ad_output & ac_pins\n\n        self._io.set_direction(io_pins, ad_pins)\n        for ad_pin in range(1 << self.PIN_COUNT):\n            ad_out = ad_pin << self.AD_OFFSET\n            ac_in = ad_to_ac(ad_out)\n            self._io.write(ad_out)\n            # random SPI exchange to ensure SPI does not change GPIO\n            self._port.exchange([0x00, 0xff], 2)\n            buf = self._io.read()\n            self.assertEqual(buf, ac_in)\n        self.assertRaises(SpiIOError, self._io.write, ac_pins)\n\n\nclass SpiUnalignedTestCase(TestCase):\n    \"\"\"Basic test for SPI with non 8-bit multiple transfer\n\n       It expects the following I/O setup:\n\n       MOSI (AD1) connected to MISO (AD2)\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n\n    def setUp(self):\n        self._spi = SpiController(cs_count=1)\n        self._spi.configure(self.url, debug=self.debug)\n        self._port = self._spi.get_port(0, freq=1E6, mode=0)\n\n    def tearDown(self):\n        \"\"\"Close the SPI connection\"\"\"\n        self._spi.terminate()\n\n    def test_invalid_write(self):\n        buf = b'\\xff\\xff'\n        self.assertRaises(ValueError, self._port.write, buf, droptail=8)\n\n    def test_bit_write(self):\n        buf = b'\\x0f'\n        for loop in range(7):\n            self._port.write(buf, droptail=loop+1)\n\n    def test_bytebit_write(self):\n        buf = b'\\xff\\xff\\x0f'\n        for loop in range(7):\n            self._port.write(buf, droptail=loop+1)\n\n    def test_invalid_read(self):\n        self.assertRaises(ValueError, self._port.read, 1, droptail=8)\n        self.assertRaises(ValueError, self._port.read, 2, droptail=8)\n\n    def test_bit_read(self):\n        # make MOSI stay to low level, so MISO samples 0\n        self._port.write([0x00])\n        for loop in range(7):\n            data = self._port.read(1, droptail=loop+1)\n            self.assertEqual(len(data), 1)\n        # make MOSI stay to high level, so MISO samples 1\n        self._port.write([0x01])\n        for loop in range(7):\n            data = self._port.read(1, droptail=loop+1)\n            self.assertEqual(len(data), 1)\n\n    def test_bytebit_read(self):\n        self._port.write([0x00])\n        for loop in range(7):\n            data = self._port.read(3, droptail=loop+1)\n            self.assertEqual(len(data), 3)\n            self.assertEqual(data[-1], 0)\n        self._port.write([0x01])\n        for loop in range(7):\n            data = self._port.read(3, droptail=loop+1)\n            self.assertEqual(len(data), 3)\n\n    def test_invalid_duplex(self):\n        buf = b'\\xff\\xff'\n        self.assertRaises(ValueError, self._port.exchange, buf,\n                          duplex=False, droptail=8)\n        self.assertRaises(ValueError, self._port.exchange, buf,\n                          duplex=False, droptail=8)\n        self.assertRaises(ValueError, self._port.exchange, buf,\n                          duplex=True, droptail=8)\n        self.assertRaises(ValueError, self._port.exchange, buf,\n                          duplex=True, droptail=8)\n\n    def test_bit_duplex(self):\n        buf = b'\\xcf'\n        for loop in range(7):\n            data = self._port.exchange(buf, duplex=True, droptail=loop+1)\n            self.assertEqual(len(data), 1)\n            exp = buf[0] & ~((1 << (loop+1))-1)\n            # print(f'{data[0]:08b} {exp:08b}')\n            self.assertEqual(data[0], exp)\n\n    def test_bytebit_duplex(self):\n        buf = b'\\xff\\xcf'\n        for loop in range(7):\n            data = self._port.exchange(buf, duplex=True, droptail=loop+1)\n            self.assertEqual(len(data), 2)\n            exp = buf[-1] & ~((1 << (loop+1))-1)\n            # print(f'{data[-1]:08b} {exp:08b}')\n            self.assertEqual(data[0], 0xFF)\n            self.assertEqual(data[-1], exp)\n\n\nclass SpiCsForceTestCase(TestCase):\n    \"\"\"Basic test for exercing direct /CS control.\n\n       It requires a scope or a digital analyzer to validate the signal\n       waveforms.\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1')\n        cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off'))\n\n    def setUp(self):\n        self._spi = SpiController(cs_count=1)\n        self._spi.configure(self.url, debug=self.debug)\n        self._port = self._spi.get_port(0, freq=1E6, mode=0)\n\n    def tearDown(self):\n        \"\"\"Close the SPI connection\"\"\"\n        self._spi.terminate()\n\n    def test_cs_default_pulse(self):\n        for _ in range(5):\n            self._port.force_select()\n\n    def test_cs_long_pulse(self):\n        for _ in range(5):\n            self._port.force_select(cs_hold=200)\n\n    def test_cs_manual_pulse(self):\n        for _ in range(5):\n            self._port.force_select(level=False)\n            self._port.force_select(level=True)\n            # beware that random USB bus access does not allow to create\n            # precise delays. This is only the shorter bound, longer one is\n            # not defined\n            sleep(100e-6)\n\n    def test_cs_pulse_write(self):\n        self._port.force_select()\n        self._port.write([0x00, 0x01, 0x02])\n\n    def test_cs_default_pulse_rev_clock(self):\n        if not self._spi.is_inverted_cpha_supported:\n            self.skipTest('FTDI does not support mode 3')\n        self._port.set_mode(3)\n        for _ in range(5):\n            self._port.force_select()\n\n\ndef suite():\n    suite_ = TestSuite()\n    loader = TestLoader()\n    mod = modules[__name__]\n    tests = (  # 'Spi',\n             'SpiGpio', 'SpiUnaligned', 'SpiCsForce')\n    for testname in tests:\n        testcase = getattr(mod, f'{testname}TestCase')\n        suite_.addTest(loader.loadTestsFromTestCase(testcase))\n    return suite_\n\n\ndef main():\n    testmod(modules[__name__])\n    FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    level = environ.get('FTDI_LOGLEVEL', 'info').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.set_level(loglevel)\n    ut_main(defaultTest='suite')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/tests/toolsimport.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright (c) 2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n\n# pylint: disable=empty-docstring\n# pylint: disable=global-statement\n# pylint: disable=invalid-name\n# pylint: disable=missing-docstring\n\nfrom doctest import testmod\nfrom importlib import import_module\nfrom os.path import dirname, join as joinpath\nfrom sys import modules, path as syspath\nfrom unittest import TestCase, TestLoader, TestSuite, main as ut_main\n\n\nclass ToolsTestCase(TestCase):\n    \"\"\"Test tool suite can be loaded.\n\n       This is especially useful to find Python syntax version mismatch\n       and other not-yet-supported modules/features.\n\n       PyFtdi and tools should support Python 3.9 onwards.\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        tools_path = joinpath(dirname(dirname(__file__)), 'bin')\n        syspath.append(tools_path)\n\n    def test_ftconf(self):\n        \"\"\"Test ftconf.py tool\"\"\"\n        mod = import_module('ftconf')\n        self.assertIsNot(getattr(mod, 'main', None), mod)\n        self.assertIsNot(mod.__doc__, None)\n\n    def test_i2cscan(self):\n        \"\"\"Test ftconf.py tool\"\"\"\n        mod = import_module('i2cscan')\n        self.assertIsNot(getattr(mod, 'main', None), mod)\n        self.assertIsNot(mod.__doc__, None)\n\n    def test_pyterm(self):\n        \"\"\"Test ftconf.py tool\"\"\"\n        mod = import_module('pyterm')\n        self.assertIsNot(getattr(mod, 'main', None), mod)\n        self.assertIsNot(mod.__doc__, None)\n\n    def test_ftdi_urls(self):\n        \"\"\"Test ftconf.py tool\"\"\"\n        mod = import_module('ftdi_urls')\n        self.assertIsNot(getattr(mod, 'main', None), mod)\n        self.assertIsNot(mod.__doc__, None)\n\n\ndef suite():\n    suite_ = TestSuite()\n    suite_.addTest(TestLoader().loadTestsFromModule(modules[__name__]))\n    return suite_\n\n\ndef main():\n    testmod(modules[__name__])\n    ut_main(defaultTest='suite')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "pyftdi/tests/uart.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"UART unit tests.\"\"\"\n\n# Copyright (c) 2017-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nimport logging\nfrom doctest import testmod\nfrom os import environ\nfrom multiprocessing import Process, set_start_method\nfrom random import choice, seed\nfrom string import printable\nfrom struct import calcsize as scalc, pack as spack, unpack as sunpack\nfrom sys import modules, platform, stdout\nfrom time import sleep, time as now\nfrom threading import Thread\nfrom unittest import TestCase, TestLoader, TestSuite, skipIf, main as ut_main\nfrom pyftdi import FtdiLogger\nfrom pyftdi.ftdi import Ftdi\nfrom pyftdi.misc import to_bool\nfrom pyftdi.serialext import serial_for_url\n\n# pylint: disable=missing-docstring\n# pylint: disable=protected-access\n\n# Specify the second port for multi port device\n# Unfortunately, auto detection triggers some issue in multiprocess test\nURL_BASE = environ.get('FTDI_DEVICE', 'ftdi:///2')\nURL = ''.join((URL_BASE[:-1], '1'))\nIFCOUNT = int(URL_BASE[-1])\n\n\nclass FtdiTestCase(TestCase):\n    \"\"\"Common features for all tests.\n    \"\"\"\n\n    @classmethod\n    def setUpClass(cls):\n        cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off'),\n                            permissive=False)\n\n    def setUp(self):\n        if self.debug:\n            print('.'.join(self.id().split('.')[-2:]))\n\n\nclass UartTestCase(FtdiTestCase):\n    \"\"\"FTDI UART driver test case\n\n       Simple test to demonstrate UART feature.\n\n       Depending on your FTDI device, you need to either:\n         * connect RXD and TXD on the unique port for 1-port FTDI device\n           (AD0 and AD1)\n         * connect RXD1 and TXD2, and RXD2 and TXD1 on the two first port of\n           2- or 4- port FTDI devices (AD0 - BD1 and AD1 - BD0)\n\n       Do NOT run this test if you use FTDI port(s) as an SPI or I2C\n       bridge -or any unsupported setup!! You've been warned.\n    \"\"\"\n\n    COUNT = 512\n\n    @classmethod\n    def setUpClass(cls):\n        FtdiTestCase.setUpClass()\n        seed()\n\n    @skipIf(IFCOUNT < 2, 'Device has not enough UART interfaces')\n    def test_uart_cross_talk_sp(self):\n        \"\"\"Exchange a random byte stream between the two first UART interfaces\n           of the same FTDI device, from the same process\n\n           This also validates PyFtdi support to use several interfaces on the\n           same FTDI device from the same Python process\n        \"\"\"\n        something_out = self.generate_bytes()\n        urla = URL\n        urlb = self.build_next_url(urla)\n        porta = serial_for_url(urla, baudrate=1000000)\n        portb = serial_for_url(urlb, baudrate=1000000)\n        try:\n            if not porta.is_open:\n                porta.open()\n            if not portb.is_open:\n                portb.open()\n            # print(\"porta: %d:%d:%d\" % porta.usb_path)\n            # print(\"portb: %d:%d:%d\" % portb.usb_path)\n            porta.timeout = 1.0\n            portb.timeout = 1.0\n            something_out = self.generate_bytes()\n            porta.write(something_out)\n            something_in = portb.read(len(something_out))\n            self.assertEqual(len(something_in), len(something_out))\n            self.assertEqual(something_in, something_out)\n            something_out = self.generate_bytes()\n            portb.write(something_out)\n            something_in = porta.read(len(something_out))\n            self.assertEqual(len(something_in), len(something_out))\n            self.assertEqual(something_in, something_out)\n        finally:\n            porta.close()\n            portb.close()\n\n    @skipIf(IFCOUNT < 2, 'Device has not enough UART interfaces')\n    @skipIf(platform == 'win32', 'Not tested on Windows')\n    def test_uart_cross_talk_mp(self):\n        if IFCOUNT > 4:\n            raise IOError('No FTDI device')\n        urla = URL\n        urlb = self.build_next_url(urla)\n        something_out = self.generate_bytes()\n        proca = Process(target=self._cross_talk_write_then_read,\n                        args=(urla, something_out))\n        procb = Process(target=self._cross_talk_read_then_write,\n                        args=(urlb, something_out))\n        # start B first to ensure RX port is open before TX port starts\n        # emitting\n        procb.start()\n        sleep(0.25)\n        proca.start()\n        proca.join(2)\n        procb.join(2)\n        # although the failure is reported (and traceback shown) in the\n        # subprocess, we still need to fail the main process test in this case\n        exita = proca.exitcode\n        exitb = procb.exitcode\n        self.assertEqual(exita, 0)\n        self.assertEqual(exitb, 0)\n\n    @skipIf(IFCOUNT != 1, 'Test reserved for single-port FTDI device')\n    def test_uart_loopback(self):\n        \"\"\"Exchange a random byte stream between the two first UART interfaces\n           of the same FTDI device, from the same process\n\n           This also validates PyFtdi support to use several interfaces on the\n           same FTDI device from the same Python process\n        \"\"\"\n        something_out = self.generate_bytes()\n        port = serial_for_url(URL, baudrate=1000000)\n        for _ in range(10):\n            try:\n                if not port.is_open:\n                    port.open()\n                port.timeout = 1.0\n                something_out = self.generate_bytes()\n                port.write(something_out)\n                something_in = port.read(len(something_out))\n                self.assertEqual(len(something_in), len(something_out))\n                self.assertEqual(something_in, something_out)\n            finally:\n                port.close()\n\n    @skipIf(IFCOUNT < 2, 'Device has not enough UART interfaces')\n    def test2_uart_cross_talk_speed(self):\n        urla = URL\n        urlb = self.build_next_url(urla)\n        porta = serial_for_url(urla, baudrate=6000000)\n        portb = serial_for_url(urlb, baudrate=6000000)\n        size = int(1e6)\n        results = [None, None]\n        chunk = 537\n        source = Thread(target=self._stream_source,\n                        args=(porta, chunk, size, results),\n                        daemon=True)\n        sink = Thread(target=self._stream_sink,\n                      args=(portb, size, results),\n                      daemon=True)\n        sink.start()\n        source.start()\n        source.join()\n        sleep(0.5)\n        sink.join()\n        if isinstance(results[1], Exception):\n            # pylint: disable=raising-bad-type\n            raise results[1]\n        # pylint: disable=unpacking-non-sequence\n        tsize, tdelta = results[0]\n        rsize, rdelta = results[1]\n        self.assertGreater(rsize, 0, 'Not data received')\n        if self.debug:\n            print(f'TX: {tsize} bytes, {tdelta*1000:.3f} ms, '\n                  f'{int(8*tsize/tdelta):d} bps')\n            print(f'RX: {rsize} bytes, {rdelta*1000:.3f} ms, '\n                  f'{int(8*rsize/rdelta):d} bps')\n        self.assertTrue(rsize >= tsize-4*chunk, 'Data loss')\n\n    @skipIf(IFCOUNT != 1, 'Test reserved for single-port FTDI device')\n    def test_loopback_talk_speed(self):\n        port = serial_for_url(URL, baudrate=6000000)\n        size = int(1e6)\n        results = [None, None]\n        chunk = 537\n        source = Thread(target=self._stream_source,\n                        args=(port, chunk, size, results),\n                        daemon=True)\n        sink = Thread(target=self._stream_sink,\n                      args=(port, size, results),\n                      daemon=True)\n        sink.start()\n        source.start()\n        source.join()\n        sleep(0.5)\n        sink.join()\n        if isinstance(results[1], Exception):\n            # pylint: disable=raising-bad-type\n            raise results[1]\n        # pylint: disable=unpacking-non-sequence\n        tsize, tdelta = results[0]\n        rsize, rdelta = results[1]\n        self.assertGreater(rsize, 0, 'Not data received')\n        if self.debug:\n            print(f'TX: {tsize} bytes, {tdelta*1000:.3f} ms, '\n                  f'{int(8*tsize/tdelta):d} bps')\n            print(f'RX: {rsize} bytes, {rdelta*1000:.3f} ms, '\n                  f'{int(8*rsize/rdelta):d} bps')\n        self.assertTrue(rsize >= tsize-4*chunk, 'Data loss')\n\n    @classmethod\n    def _stream_source(cls, port, chunk, size, results):\n        pos = 0\n        tx_size = 0\n        start = now()\n        while tx_size < size:\n            samples = spack(f'>{chunk}I', *range(pos, pos+chunk))\n            pos += chunk\n            port.write(samples)\n            tx_size += len(samples)\n            if results[1] is not None:\n                break\n        delta = now()-start\n        results[0] = tx_size, delta\n\n    @classmethod\n    def _stream_sink(cls, port, size, results):\n        pos = 0\n        first = None\n        data = bytearray()\n        sample_size = scalc('>I')\n        rx_size = 0\n        port.timeout = 1.0\n        start = now()\n        while rx_size < size:\n            buf = port.read(1024)\n            if not buf:\n                print('T')\n                break\n            rx_size += len(buf)\n            data.extend(buf)\n            sample_count = len(data)//sample_size\n            length = sample_count*sample_size\n            samples = sunpack(f'>{sample_count}I' % sample_count,\n                              data[:length])\n            data = data[length:]\n            for sample in samples:\n                if first is None:\n                    first = sample\n                    pos = sample\n                    continue\n                pos += 1\n                if sample != pos:\n                    results[1] = AssertionError('Lost byte @ %d', pos-first)\n                    return\n        delta = now()-start\n        results[1] = (rx_size, delta)\n\n    @classmethod\n    def _cross_talk_write_then_read(cls, url, refstream):\n        # use classmethod & direct AssertionError to avoid pickler issues\n        # with multiprocessing:\n        # \"TypeError: cannot serialize '_io.TextIOWrapper' object\"\n        port = serial_for_url(url, baudrate=1000000)\n        try:\n            if not port.is_open:\n                port.open()\n            port.timeout = 5.0\n            port.write(refstream)\n            instream = port.read(len(refstream))\n            if len(instream) != len(refstream):\n                raise AssertionError('Stream length differ')\n            # we expect the peer to return the same stream, inverted\n            localstream = bytes(reversed(instream))\n            if localstream != refstream:\n                raise AssertionError('Stream content differ')\n        finally:\n            port.close()\n\n    @classmethod\n    def _cross_talk_read_then_write(cls, url, refstream):\n        # use classmethod & direct AssertionError to avoid pickler issues\n        # with multiprocessing:\n        # \"TypeError: cannot serialize '_io.TextIOWrapper' object\"\n        port = serial_for_url(url, baudrate=1000000)\n        try:\n            if not port.is_open:\n                port.open()\n            port.timeout = 5.0\n            instream = port.read(len(refstream))\n            if len(instream) != len(refstream):\n                raise AssertionError('Stream length differ')\n            if instream != refstream:\n                raise AssertionError('Stream content differ')\n            # the peer expect us to return the same stream, inverted\n            outstream = bytes(reversed(instream))\n            port.write(outstream)\n        finally:\n            port.close()\n\n    @classmethod\n    def generate_bytes(cls, count=0):\n        return ''.join([choice(printable)\n                        for x in range(count or cls.COUNT)]).encode()\n\n    @classmethod\n    def build_next_url(cls, url):\n        iface = int(url[-1])\n        iface = (iface + 1) % 3\n        return f'{url[:-1]}{iface}'\n\n\nclass BaudrateTestCase(FtdiTestCase):\n    \"\"\"Simple test to check clock stretching cannot be overwritten with\n       GPIOs.\n    \"\"\"\n\n    BAUDRATES = (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200,\n                 230400, 460800, 576000, 921600, 1000000, 1500000, 1843200,\n                 2000000, 2500000, 3000000, 4000000, 6000000, 8000000,\n                 12000000)\n\n    def test(self):\n        ftdi = Ftdi()\n        ftdi.open_from_url(URL)\n        for baudrate in self.BAUDRATES:\n            actual, _, _ = ftdi._convert_baudrate(baudrate)\n            ratio = baudrate/actual\n            self.assertTrue(0.97 <= ratio <= 1.03, \"Invalid baudrate\")\n\n\ndef suite():\n    suite_ = TestSuite()\n    suite_.addTest(TestLoader().loadTestsFromModule(modules[__name__]))\n    return suite_\n\n\ndef main():\n    testmod(modules[__name__])\n    FtdiLogger.log.addHandler(logging.StreamHandler(stdout))\n    level = environ.get('FTDI_LOGLEVEL', 'info').upper()\n    try:\n        loglevel = getattr(logging, level)\n    except AttributeError as exc:\n        raise ValueError(f'Invalid log level: {level}') from exc\n    FtdiLogger.set_level(loglevel)\n    ut_main(defaultTest='suite')\n\n\nif __name__ == '__main__':\n    if platform == 'darwin':\n        # avoid the infamous \"The process has forked and you cannot use this\n        # CoreFoundation functionality safely. You MUST exec().\" error on macOS\n        set_start_method('spawn')\n    main()\n"
  },
  {
    "path": "pyftdi/tracer.py",
    "content": "# Copyright (c) 2017-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"MPSSE command debug tracer.\"\"\"\n\n# pylint: disable=missing-docstring\n\nfrom binascii import hexlify\nfrom collections import deque\nfrom importlib import import_module\nfrom inspect import currentframe\nfrom logging import getLogger\nfrom string import ascii_uppercase\nfrom struct import unpack as sunpack\nfrom sys import modules\nfrom typing import Union\n\n\nclass FtdiMpsseTracer:\n    \"\"\"FTDI MPSSE protocol decoder.\"\"\"\n\n    MPSSE_ENGINES = {\n        0x0200: 0,\n        0x0400: 0,\n        0x0500: 0,\n        0x0600: 0,\n        0x0700: 2,\n        0x0800: 2,\n        0x0900: 1,\n        0x1000: 0,\n        0x3600: 2}\n    \"\"\"Count of MPSSE engines.\"\"\"\n\n    def __init__(self, version):\n        count = self.MPSSE_ENGINES[version]\n        self._engines = [None] * count\n\n    def send(self, iface: int, buf: Union[bytes, bytearray]) -> None:\n        self._get_engine(iface).send(buf)\n\n    def receive(self, iface: int, buf: Union[bytes, bytearray]) -> None:\n        self._get_engine(iface).receive(buf)\n\n    def _get_engine(self, iface: int):\n        iface -= 1\n        try:\n            self._engines[iface]\n        except IndexError as exc:\n            raise ValueError(f'No MPSSE engine available on interface '\n                             f'{iface}') from exc\n        if not self._engines[iface]:\n            self._engines[iface] = FtdiMpsseEngine(iface)\n        return self._engines[iface]\n\n\nclass FtdiMpsseEngine:\n    \"\"\"FTDI MPSSE virtual engine\n\n       Far from being complete for now\n    \"\"\"\n\n    COMMAND_PREFIX = \\\n        'GET SET READ WRITE RW ENABLE DISABLE CLK LOOPBACK SEND DRIVE'\n\n    ST_IDLE = range(1)\n\n    def __init__(self, iface: int):\n        self.log = getLogger('pyftdi.mpsse.tracer')\n        self._if = iface\n        self._trace_tx = bytearray()\n        self._trace_rx = bytearray()\n        self._state = self.ST_IDLE\n        self._clkdiv5 = False\n        self._cmd_decoded = True\n        self._resp_decoded = True\n        self._last_codes = deque()\n        self._expect_resp = deque()  # positive: byte, negative: bit count\n        self._commands = self._build_commands()\n\n    def send(self, buf: Union[bytes, bytearray]) -> None:\n        self._trace_tx.extend(buf)\n        while self._trace_tx:\n            try:\n                code = self._trace_tx[0]\n                cmd = self._commands[code]\n                if self._cmd_decoded:\n                    self.log.debug('[%d]:[Command: %02X: %s]',\n                                   self._if, code, cmd)\n                cmd_decoder = getattr(self, f'_cmd_{cmd.lower()}')\n                rdepth = len(self._expect_resp)\n                try:\n                    self._cmd_decoded = cmd_decoder()\n                except AttributeError as exc:\n                    raise ValueError(str(exc)) from exc\n                if len(self._expect_resp) > rdepth:\n                    self._last_codes.append(code)\n                if self._cmd_decoded:\n                    continue\n                # not enough data in buffer to decode a whole command\n                return\n            except IndexError:\n                self.log.warning('[%d]:Empty buffer on %02X: %s',\n                                 self._if, code, cmd)\n            except KeyError:\n                self.log.warning('[%d]:Unknown command code: %02X',\n                                 self._if, code)\n            except AttributeError:\n                self.log.warning('[%d]:Decoder for command %s [%02X] is not '\n                                 'implemented', self._if, cmd, code)\n            except ValueError as exc:\n                self.log.warning('[%d]:Decoder for command %s [%02X] failed: '\n                                 '%s', self._if, cmd, code, exc)\n            # on error, flush all buffers\n            self.log.warning('Flush TX/RX buffers')\n            self._trace_tx = bytearray()\n            self._trace_rx = bytearray()\n            self._last_codes.clear()\n\n    def receive(self, buf: Union[bytes, bytearray]) -> None:\n        self.log.info(' .. %s', hexlify(buf).decode())\n        self._trace_rx.extend(buf)\n        while self._trace_rx:\n            code = None\n            try:\n                code = self._last_codes.popleft()\n                cmd = self._commands[code]\n                resp_decoder = getattr(self, f'_resp_{cmd.lower()}')\n                self._resp_decoded = resp_decoder()\n                if self._resp_decoded:\n                    continue\n                # not enough data in buffer to decode a whole response\n                return\n            except IndexError:\n                self.log.warning('[%d]:Empty buffer', self._if)\n            except KeyError:\n                self.log.warning('[%d]:Unknown command code: %02X',\n                                 self._if, code)\n            except AttributeError:\n                self.log.warning('[%d]:Decoder for response %s [%02X] is not '\n                                 'implemented', self._if, cmd, code)\n            # on error, flush RX buffer\n            self.log.warning('[%d]:Flush RX buffer', self._if)\n            self._trace_rx = bytearray()\n            self._last_codes.clear()\n\n    @classmethod\n    def _build_commands(cls):\n        # pylint: disable=no-self-argument\n        commands = {}\n        fdti_mod_name = 'pyftdi.ftdi'\n        ftdi_mod = modules.get(fdti_mod_name)\n        if not ftdi_mod:\n            ftdi_mod = import_module(fdti_mod_name)\n        ftdi_type = getattr(ftdi_mod, 'Ftdi')\n        for cmd in dir(ftdi_type):\n            if cmd[0] not in ascii_uppercase:\n                continue\n            value = getattr(ftdi_type, cmd)\n            if not isinstance(value, int):\n                continue\n            family = cmd.split('_')[0]\n            # pylint: disable=no-member\n            if family not in cls.COMMAND_PREFIX.split():\n                continue\n            commands[value] = cmd\n        return commands\n\n    def _cmd_enable_clk_div5(self):\n        self.log.info(' [%d]:Enable clock divisor /5', self._if)\n        self._clkdiv5 = True\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_disable_clk_div5(self):\n        self.log.info(' [%d]:Disable clock divisor /5', self._if)\n        self._clkdiv5 = False\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_set_tck_divisor(self):\n        if len(self._trace_tx) < 3:\n            return False\n        value, = sunpack('<H', self._trace_tx[1:3])\n        base = 12E6 if self._clkdiv5 else 60E6\n        freq = base / ((1 + value) * 2)\n        self.log.info(' [%d]:Set frequency %.3fMHZ', self._if, freq/1E6)\n        self._trace_tx[:] = self._trace_tx[3:]\n        return True\n\n    def _cmd_loopback_end(self):\n        self.log.info(' [%d]:Disable loopback', self._if)\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_enable_clk_adaptive(self):\n        self.log.info(' [%d]:Enable adaptive clock', self._if)\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_disable_clk_adaptive(self):\n        self.log.info(' [%d]:Disable adaptive clock', self._if)\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_enable_clk_3phase(self):\n        self.log.info(' [%d]:Enable 3-phase clock', self._if)\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_disable_clk_3phase(self):\n        self.log.info(' [%d]:Disable 3-phase clock', self._if)\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_drive_zero(self):\n        if len(self._trace_tx) < 3:\n            return False\n        value, = sunpack('H', self._trace_tx[1:3])\n        self.log.info(' [%d]:Open collector [15:0] %04x %s',\n                      self._if, value, self.bitfmt(value, 16))\n        self._trace_tx[:] = self._trace_tx[3:]\n        return True\n\n    def _cmd_send_immediate(self):\n        self.log.debug(' [%d]:Send immediate', self._if)\n        self._trace_tx[:] = self._trace_tx[1:]\n        return True\n\n    def _cmd_get_bits_low(self):\n        self._trace_tx[:] = self._trace_tx[1:]\n        self._expect_resp.append(1)\n        return True\n\n    def _cmd_get_bits_high(self):\n        self._trace_tx[:] = self._trace_tx[1:]\n        self._expect_resp.append(1)\n        return True\n\n    def _cmd_set_bits_low(self):\n        if len(self._trace_tx) < 3:\n            return False\n        value, direction = sunpack('BB', self._trace_tx[1:3])\n        self.log.info(' [%d]:Set gpio[7:0]  %02x %s',\n                      self._if, value, self.bm2str(value, direction))\n        self._trace_tx[:] = self._trace_tx[3:]\n        return True\n\n    def _cmd_set_bits_high(self):\n        if len(self._trace_tx) < 3:\n            return False\n        value, direction = sunpack('BB', self._trace_tx[1:3])\n        self.log.info(' [%d]:Set gpio[15:8] %02x %s',\n                      self._if, value, self.bm2str(value, direction))\n        self._trace_tx[:] = self._trace_tx[3:]\n        return True\n\n    def _cmd_write_bytes_pve_msb(self):\n        return self._decode_output_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_write_bytes_nve_msb(self):\n        return self._decode_output_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_write_bytes_pve_lsb(self):\n        return self._decode_output_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_write_bytes_nve_lsb(self):\n        return self._decode_output_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_read_bytes_pve_msb(self):\n        return self._decode_input_mpsse_byte_request()\n\n    def _resp_read_bytes_pve_msb(self):\n        return self._decode_input_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_read_bytes_nve_msb(self):\n        return self._decode_input_mpsse_byte_request()\n\n    def _resp_read_bytes_nve_msb(self):\n        return self._decode_input_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_read_bytes_pve_lsb(self):\n        return self._decode_input_mpsse_byte_request()\n\n    def _resp_read_bytes_pve_lsb(self):\n        return self._decode_input_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_read_bytes_nve_lsb(self):\n        return self._decode_input_mpsse_byte_request()\n\n    def _resp_read_bytes_nve_lsb(self):\n        return self._decode_input_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_rw_bytes_nve_pve_msb(self):\n        return self._decode_output_mpsse_bytes(currentframe().f_code.co_name,\n                                               True)\n\n    def _resp_rw_bytes_nve_pve_msb(self):\n        return self._decode_input_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_rw_bytes_pve_nve_msb(self):\n        return self._decode_output_mpsse_bytes(currentframe().f_code.co_name,\n                                               True)\n\n    def _resp_rw_bytes_pve_nve_msb(self):\n        return self._decode_input_mpsse_bytes(currentframe().f_code.co_name)\n\n    def _cmd_write_bits_pve_msb(self):\n        return self._decode_output_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_write_bits_nve_msb(self):\n        return self._decode_output_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_write_bits_pve_lsb(self):\n        return self._decode_output_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_write_bits_nve_lsb(self):\n        return self._decode_output_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_read_bits_pve_msb(self):\n        return self._decode_input_mpsse_bit_request()\n\n    def _resp_read_bits_pve_msb(self):\n        return self._decode_input_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_read_bits_nve_msb(self):\n        return self._decode_input_mpsse_bit_request()\n\n    def _resp_read_bits_nve_msb(self):\n        return self._decode_input_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_read_bits_pve_lsb(self):\n        return self._decode_input_mpsse_bit_request()\n\n    def _resp_read_bits_pve_lsb(self):\n        return self._decode_input_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_read_bits_nve_lsb(self):\n        return self._decode_input_mpsse_bit_request()\n\n    def _resp_read_bits_nve_lsb(self):\n        return self._decode_input_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_rw_bits_nve_pve_msb(self):\n        return self._decode_output_mpsse_bits(currentframe().f_code.co_name,\n                                              True)\n\n    def _resp_rw_bits_nve_pve_msb(self):\n        return self._decode_input_mpsse_bits(currentframe().f_code.co_name)\n\n    def _cmd_rw_bits_pve_nve_msb(self):\n        return self._decode_output_mpsse_bits(currentframe().f_code.co_name,\n                                              True)\n\n    def _resp_rw_bits_pve_nve_msb(self):\n        return self._decode_input_mpsse_bits(currentframe().f_code.co_name)\n\n    def _resp_get_bits_low(self):\n        if self._trace_rx:\n            return False\n        value = self._trace_rx[0]\n        self.log.info(' [%d]:Get gpio[7:0]  %02x %s',\n                      self._if, value, self.bm2str(value, 0xFF))\n        self._trace_rx[:] = self._trace_rx[1:]\n        return True\n\n    def _resp_get_bits_high(self):\n        if self._trace_rx:\n            return False\n        value = self._trace_rx[0]\n        self.log.info(' [%d]:Get gpio[15:8] %02x %s',\n                      self._if, value, self.bm2str(value, 0xFF))\n        self._trace_rx[:] = self._trace_rx[1:]\n        return True\n\n    def _decode_output_mpsse_bytes(self, caller, expect_rx=False):\n        if len(self._trace_tx) < 4:\n            return False\n        length = sunpack('<H', self._trace_tx[1:3])[0] + 1\n        if len(self._trace_tx) < 4 + length:\n            return False\n        if expect_rx:\n            self._expect_resp.append(length)\n        payload = self._trace_tx[3:3+length]\n        funcname = caller[5:].title().replace('_', '')\n        self.log.info(' [%d]:%s> (%d) %s',\n                      self._if, funcname, length,\n                      hexlify(payload).decode('utf8'))\n        self._trace_tx[:] = self._trace_tx[3+length:]\n        return True\n\n    def _decode_output_mpsse_bits(self, caller, expect_rx=False):\n        if len(self._trace_tx) < 3:\n            return False\n        bitlen = self._trace_tx[1] + 1\n        if expect_rx:\n            self._expect_resp.append(-bitlen)\n        payload = self._trace_tx[2]\n        funcname = caller[5:].title().replace('_', '')\n        msb = caller[5:][-3].lower() == 'm'\n        self.log.info(' %s> (%d) %s',\n                      funcname, bitlen, self.bit2str(payload, bitlen, msb))\n        self._trace_tx[:] = self._trace_tx[3:]\n        return True\n\n    def _decode_input_mpsse_byte_request(self):\n        if len(self._trace_tx) < 3:\n            return False\n        length = sunpack('<H', self._trace_tx[1:3])[0] + 1\n        self._expect_resp.append(length)\n        self._trace_tx[:] = self._trace_tx[3:]\n        return True\n\n    def _decode_input_mpsse_bit_request(self):\n        if len(self._trace_tx) < 2:\n            return False\n        bitlen = self._trace_tx[1] + 1\n        self._expect_resp.append(-bitlen)\n        self._trace_tx[:] = self._trace_tx[2:]\n        return True\n\n    def _decode_input_mpsse_bytes(self, caller):\n        if not self._expect_resp:\n            self.log.warning('[%d]:Response w/o request?', self._if)\n            return False\n        if self._expect_resp[0] < 0:\n            self.log.warning('[%d]:Handling byte request w/ bit length',\n                             self._if)\n            return False\n        if len(self._trace_rx) < self._expect_resp[0]:  # peek\n            return False\n        length = self._expect_resp.popleft()\n        payload = self._trace_rx[:length]\n        self._trace_rx[:] = self._trace_rx[length:]\n        funcname = caller[5:].title().replace('_', '')\n        self.log.info(' %s< (%d) %s',\n                      funcname, length, hexlify(payload).decode('utf8'))\n        return True\n\n    def _decode_input_mpsse_bits(self, caller):\n        if not self._expect_resp:\n            self.log.warning('[%d]:Response w/o request?', self._if)\n            return False\n        if not self._trace_rx:  # peek\n            return False\n        if self._expect_resp[0] > 0:\n            self.log.warning('[%d]:Handling bit request w/ byte length',\n                             self._if)\n        bitlen = -self._expect_resp.popleft()\n        payload = self._trace_rx[0]\n        self._trace_rx[:] = self._trace_rx[1:]\n        funcname = caller[5:].title().replace('_', '')\n        msb = caller[5:][-3].lower() == 'm'\n        self.log.info(' %s< (%d) %s',\n                      funcname, bitlen, self.bit2str(payload, bitlen, msb))\n        return True\n\n    @classmethod\n    def bit2str(cls, value: int, count: int, msb: bool, hiz: str = '_') -> str:\n        mask = (1 << count) - 1\n        if msb:\n            mask <<= 8 - count\n        return cls.bm2str(value, mask, hiz)\n\n    @classmethod\n    def bm2str(cls, value: int, mask: int, hiz: str = '_') -> str:\n        vstr = cls.bitfmt(value, 8)\n        mstr = cls.bitfmt(mask, 8)\n        return ''.join([m == '1' and v or hiz for v, m in zip(vstr, mstr)])\n\n    @classmethod\n    def bitfmt(cls, value, width):\n        return format(value, f'0{width}b')\n\n    # rw_bytes_pve_pve_lsb\n    # rw_bytes_pve_nve_lsb\n    # rw_bytes_nve_pve_lsb\n    # rw_bytes_nve_nve_lsb\n    # rw_bits_pve_pve_lsb\n    # rw_bits_pve_nve_lsb\n    # rw_bits_nve_pve_lsb\n    # rw_bits_nve_nve_lsb\n    # write_bits_tms_pve\n    # write_bits_tms_nve\n    # rw_bits_tms_pve_pve\n    # rw_bits_tms_nve_pve\n    # rw_bits_tms_pve_nve\n    # rw_bits_tms_nve_nve\n"
  },
  {
    "path": "pyftdi/usbtools.py",
    "content": "# Copyright (c) 2014-2024, Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2016, Emmanuel Bouaziz <ebouaziz@free.fr>\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n\"\"\"USB Helpers\"\"\"\n\nimport sys\nfrom importlib import import_module\nfrom string import printable as printablechars\nfrom threading import RLock\nfrom typing import (Any, Dict, List, NamedTuple, Optional, Sequence, Set,\n                    TextIO, Type, Tuple, Union)\nfrom urllib.parse import SplitResult, urlsplit, urlunsplit\nfrom usb.backend import IBackend\nfrom usb.core import Device as UsbDevice, USBError\nfrom usb.util import dispose_resources, get_string as usb_get_string\nfrom .misc import to_int\n\n# pylint: disable=broad-except\n\nUsbDeviceDescriptor = NamedTuple('UsbDeviceDescriptor',\n                                 (('vid', int),\n                                  ('pid', int),\n                                  ('bus', Optional[int]),\n                                  ('address', Optional[int]),\n                                  ('sn', Optional[str]),\n                                  ('index', Optional[int]),\n                                  ('description', Optional[str])))\n\"\"\"USB Device descriptor are used to report known information about a FTDI\n   compatible device, and as a device selection filter\n\n   * vid: vendor identifier, 16-bit integer\n   * pid: product identifier, 16-bit integer\n   * bus: USB bus identifier, host dependent integer\n   * address: USB address identifier on a USB bus, host dependent integer\n   * sn: serial number, string\n   * index: integer, can be used to descriminate similar devices\n   * description: device description, as a string\n\n   To select a device, use None for unknown fields\n\n   .. note::\n\n     * Always prefer serial number to other identification methods if available\n     * Prefer bus/address selector over index\n\"\"\"\n\nUsbDeviceKey = Union[Tuple[int, int, int, int], Tuple[int, int]]\n\"\"\"USB device indentifier on the system.\n\n   This is used as USB device identifiers on the host. On proper hosts,\n   this is a (bus, address, vid, pid) 4-uple. On stupid hosts (such as M$Win),\n   it may be degraded to (vid, pid) 2-uple.\n\"\"\"\n\n\nclass UsbToolsError(Exception):\n    \"\"\"UsbTools error.\"\"\"\n\n\nclass UsbTools:\n    \"\"\"Helpers to obtain information about connected USB devices.\"\"\"\n\n    # Supported back ends, in preference order\n    BACKENDS = ('usb.backend.libusb1', 'usb.backend.libusb0')\n\n    # Need to maintain a list of reference USB devices, to circumvent a\n    # limitation in pyusb that prevents from opening several times the same\n    # USB device. The following dictionary used bus/address/vendor/product keys\n    # to track (device, refcount) pairs\n    Lock = RLock()\n    Devices = {}  # (bus, address, vid, pid): (usb.core.Device, refcount)\n    UsbDevices = {}  # (vid, pid): {usb.core.Device}\n    UsbApi = None\n\n    @classmethod\n    def find_all(cls, vps: Sequence[Tuple[int, int]],\n                 nocache: bool = False) -> \\\n            List[Tuple[UsbDeviceDescriptor, int]]:\n        \"\"\"Find all devices that match the specified vendor/product pairs.\n\n           :param vps: a sequence of 2-tuple (vid, pid) pairs\n           :param bool nocache: bypass cache to re-enumerate USB devices on\n                                the host\n           :return: a list of 2-tuple (UsbDeviceDescriptor, interface count)\n        \"\"\"\n        with cls.Lock:\n            devs = set()\n            for vid, pid in vps:\n                # TODO optimize useless loops\n                devs.update(UsbTools._find_devices(vid, pid, nocache))\n            devices = set()\n            for dev in devs:\n                ifcount = max(cfg.bNumInterfaces for cfg in dev)\n                # TODO: handle / is serial number strings\n                sernum = UsbTools.get_string(dev, dev.iSerialNumber)\n                description = UsbTools.get_string(dev, dev.iProduct)\n                descriptor = UsbDeviceDescriptor(dev.idVendor, dev.idProduct,\n                                                 dev.bus, dev.address,\n                                                 sernum, None, description)\n                devices.add((descriptor, ifcount))\n            return list(devices)\n\n    @classmethod\n    def flush_cache(cls, ):\n        \"\"\"Flush the FTDI device cache.\n\n           It is highly recommanded to call this method a FTDI device is\n           unplugged/plugged back since the last enumeration, as the device\n           may appear on a different USB location each time it is plugged\n           in.\n\n           Failing to clear out the cache may lead to USB Error 19:\n           ``Device may have been disconnected``.\n        \"\"\"\n        with cls.Lock:\n            cls.UsbDevices.clear()\n\n    @classmethod\n    def get_device(cls, devdesc: UsbDeviceDescriptor) -> UsbDevice:\n        \"\"\"Find a previously open device with the same vendor/product\n           or initialize a new one, and return it.\n\n           If several FTDI devices of the same kind (vid, pid) are connected\n           to the host, either index or serial argument should be used to\n           discriminate the FTDI device.\n\n           index argument is not a reliable solution as the host may enumerate\n           the USB device in random order. serial argument is more reliable\n           selector and should always be prefered.\n\n           Some FTDI devices support several interfaces/ports (such as FT2232H,\n           FT4232H and FT4232HA). The interface argument selects the FTDI port\n           to use, starting from 1 (not 0).\n\n           :param devdesc: Device descriptor that identifies the device by\n                           constraints.\n           :return: PyUSB device instance\n        \"\"\"\n        with cls.Lock:\n            if devdesc.index or devdesc.sn or devdesc.description:\n                dev = None\n                if not devdesc.vid:\n                    raise ValueError('Vendor identifier is required')\n                devs = cls._find_devices(devdesc.vid, devdesc.pid)\n                if devdesc.description:\n                    devs = [dev for dev in devs if\n                            UsbTools.get_string(dev, dev.iProduct) ==\n                            devdesc.description]\n                if devdesc.sn:\n                    devs = [dev for dev in devs if\n                            UsbTools.get_string(dev, dev.iSerialNumber) ==\n                            devdesc.sn]\n                if devdesc.bus is not None and devdesc.address is not None:\n                    devs = [dev for dev in devs if\n                            (devdesc.bus == dev.bus and\n                             devdesc.address == dev.address)]\n                if isinstance(devs, set):\n                    # there is no guarantee the same index with lead to the\n                    # same device. Indexing should be reworked\n                    devs = list(devs)\n                try:\n                    dev = devs[devdesc.index or 0]\n                except IndexError as exc:\n                    raise IOError(\"No such device\") from exc\n            else:\n                devs = cls._find_devices(devdesc.vid, devdesc.pid)\n                dev = list(devs)[0] if devs else None\n            if not dev:\n                raise IOError('Device not found')\n            try:\n                devkey = (dev.bus, dev.address, devdesc.vid, devdesc.pid)\n                if None in devkey[0:2]:\n                    raise AttributeError('USB backend does not support bus '\n                                         'enumeration')\n            except AttributeError:\n                devkey = (devdesc.vid, devdesc.pid)\n            if devkey not in cls.Devices:\n                # only change the active configuration if the active one is\n                # not the first. This allows other libusb sessions running\n                # with the same device to run seamlessly.\n                try:\n                    config = dev.get_active_configuration()\n                    setconf = config.bConfigurationValue != 1\n                except USBError:\n                    setconf = True\n                if setconf:\n                    try:\n                        dev.set_configuration()\n                    except USBError:\n                        pass\n                cls.Devices[devkey] = [dev, 1]\n            else:\n                cls.Devices[devkey][1] += 1\n            return cls.Devices[devkey][0]\n\n    @classmethod\n    def release_device(cls, usb_dev: UsbDevice):\n        \"\"\"Release a previously open device, if it not used anymore.\n\n           :param usb_dev: a previously instanciated USB device instance\n        \"\"\"\n        # Lookup for ourselves in the class dictionary\n        with cls.Lock:\n            # pylint: disable=unnecessary-dict-index-lookup\n            for devkey, (dev, refcount) in cls.Devices.items():\n                if dev == usb_dev:\n                    # found\n                    if refcount > 1:\n                        # another interface is open, decrement\n                        cls.Devices[devkey][1] -= 1\n                    else:\n                        # last interface in use, release\n                        dispose_resources(cls.Devices[devkey][0])\n                        del cls.Devices[devkey]\n                    break\n\n    @classmethod\n    def release_all_devices(cls, devclass: Optional[Type] = None) -> int:\n        \"\"\"Release all open devices.\n\n           :param devclass: optional class to only release devices of one type\n           :return: the count of device that have been released.\n        \"\"\"\n        with cls.Lock:\n            remove_devs = set()\n            # pylint: disable=consider-using-dict-items\n            for devkey in cls.Devices:\n                if devclass:\n                    dev = cls._get_backend_device(cls.Devices[devkey][0])\n                    if dev is None or not isinstance(dev, devclass):\n                        continue\n                dispose_resources(cls.Devices[devkey][0])\n                remove_devs.add(devkey)\n            for devkey in remove_devs:\n                del cls.Devices[devkey]\n            return len(remove_devs)\n\n    @classmethod\n    def list_devices(cls, urlstr: str,\n                     vdict: Dict[str, int],\n                     pdict: Dict[int, Dict[str, int]],\n                     default_vendor: int) -> \\\n            List[Tuple[UsbDeviceDescriptor, int]]:\n        \"\"\"List candidates that match the device URL pattern.\n\n           :see: :py:meth:`show_devices` to generate the URLs from the\n                 candidates list\n\n           :param url: the URL to parse\n           :param vdict: vendor name map of USB vendor ids\n           :param pdict: vendor id map of product name map of product ids\n           :param default_vendor: default vendor id\n           :return: list of (UsbDeviceDescriptor, interface)\n        \"\"\"\n        urlparts = urlsplit(urlstr)\n        if not urlparts.path:\n            raise UsbToolsError('URL string is missing device port')\n        candidates, _ = cls.enumerate_candidates(urlparts, vdict, pdict,\n                                                 default_vendor)\n        return candidates\n\n    @classmethod\n    def parse_url(cls, urlstr: str, scheme: str,\n                  vdict: Dict[str, int],\n                  pdict: Dict[int, Dict[str, int]],\n                  default_vendor: int) -> Tuple[UsbDeviceDescriptor, int]:\n        \"\"\"Parse a device specifier URL.\n\n           :param url: the URL to parse\n           :param scheme: scheme to match in the URL string (scheme://...)\n           :param vdict: vendor name map of USB vendor ids\n           :param pdict: vendor id map of product name map of product ids\n           :param default_vendor: default vendor id\n           :return: UsbDeviceDescriptor, interface\n\n           ..note:\n\n              URL syntax:\n\n                  protocol://vendor:product[:serial|:index|:bus:addr]/interface\n        \"\"\"\n        urlparts = urlsplit(urlstr)\n        if scheme != urlparts.scheme:\n            raise UsbToolsError(f'Invalid URL: {urlstr}')\n        try:\n            if not urlparts.path:\n                raise UsbToolsError('URL string is missing device port')\n            path = urlparts.path.strip('/')\n            if path == '?' or (not path and urlstr.endswith('?')):\n                report_devices = True\n                interface = -1\n            else:\n                interface = to_int(path)\n                report_devices = False\n        except (IndexError, ValueError) as exc:\n            raise UsbToolsError(f'Invalid device URL: {urlstr}') from exc\n        candidates, idx = cls.enumerate_candidates(urlparts, vdict, pdict,\n                                                   default_vendor)\n        if report_devices:\n            UsbTools.show_devices(scheme, vdict, pdict, candidates)\n            raise SystemExit(candidates and\n                             'Please specify the USB device' or\n                             'No USB-Serial device has been detected')\n        if idx is None:\n            if len(candidates) > 1:\n                raise UsbToolsError(f\"{len(candidates)} USB devices match URL \"\n                                    f\"'{urlstr}'\")\n            idx = 0\n        try:\n            desc, _ = candidates[idx]\n            vendor, product = desc[:2]\n        except IndexError:\n            raise UsbToolsError(f'No USB device matches URL {urlstr}') \\\n                from None\n        if not vendor:\n            cvendors = {candidate[0] for candidate in candidates}\n            if len(cvendors) == 1:\n                vendor = cvendors.pop()\n        if vendor not in pdict:\n            vstr = '0x{vendor:04x}' if vendor is not None else '?'\n            raise UsbToolsError(f'Vendor ID {vstr} not supported')\n        if not product:\n            cproducts = {candidate[1] for candidate in candidates\n                         if candidate[0] == vendor}\n            if len(cproducts) == 1:\n                product = cproducts.pop()\n        if product not in pdict[vendor].values():\n            pstr = '0x{vendor:04x}' if product is not None else '?'\n            raise UsbToolsError(f'Product ID {pstr} not supported')\n        devdesc = UsbDeviceDescriptor(vendor, product, desc.bus, desc.address,\n                                      desc.sn, idx, desc.description)\n        return devdesc, interface\n\n    @classmethod\n    def enumerate_candidates(cls, urlparts: SplitResult,\n                             vdict: Dict[str, int],\n                             pdict: Dict[int, Dict[str, int]],\n                             default_vendor: int) -> \\\n            Tuple[List[Tuple[UsbDeviceDescriptor, int]], Optional[int]]:\n        \"\"\"Enumerate USB device URLs that match partial URL and VID/PID\n           criteria.\n\n           :param urlpart: splitted device specifier URL\n           :param vdict: vendor name map of USB vendor ids\n           :param pdict: vendor id map of product name map of product ids\n           :param default_vendor: default vendor id\n           :return: list of (usbdev, iface), parsed index if any\n        \"\"\"\n        specifiers = urlparts.netloc.split(':')\n        plcomps = specifiers + [''] * 2\n        try:\n            plcomps[0] = vdict.get(plcomps[0], plcomps[0])\n            if plcomps[0]:\n                vendor = to_int(plcomps[0])\n            else:\n                vendor = None\n            product_ids = pdict.get(vendor, None)\n            if not product_ids:\n                product_ids = pdict[default_vendor]\n            plcomps[1] = product_ids.get(plcomps[1], plcomps[1])\n            if plcomps[1]:\n                try:\n                    product = to_int(plcomps[1])\n                except ValueError as exc:\n                    raise UsbToolsError(f'Product {plcomps[1]} is not '\n                                        f'referenced') from exc\n            else:\n                product = None\n        except (IndexError, ValueError) as exc:\n            raise UsbToolsError(f'Invalid device URL: '\n                                f'{urlunsplit(urlparts)}') from exc\n        sernum = None\n        idx = None\n        bus = None\n        address = None\n        locators = specifiers[2:]\n        if len(locators) > 1:\n            try:\n                bus = int(locators[0], 16)\n                address = int(locators[1], 16)\n            except ValueError as exc:\n                raise UsbToolsError(f'Invalid bus/address: '\n                                    f'{\":\".join(locators)}') from exc\n        else:\n            if locators and locators[0]:\n                try:\n                    devidx = to_int(locators[0])\n                    if devidx > 255:\n                        raise ValueError()\n                    idx = devidx\n                    if idx:\n                        idx = devidx-1\n                except ValueError:\n                    sernum = locators[0]\n        candidates = []\n        vendors = [vendor] if vendor else set(vdict.values())\n        vps = set()\n        for vid in vendors:\n            products = pdict.get(vid, [])\n            for pid in products:\n                vps.add((vid, products[pid]))\n        devices = cls.find_all(vps)\n        if sernum:\n            if sernum not in [dev.sn for dev, _ in devices]:\n                raise UsbToolsError(f'No USB device with S/N {sernum}')\n        for desc, ifcount in devices:\n            if vendor and vendor != desc.vid:\n                continue\n            if product and product != desc.pid:\n                continue\n            if sernum and sernum != desc.sn:\n                continue\n            if bus is not None:\n                if bus != desc.bus or address != desc.address:\n                    continue\n            candidates.append((desc, ifcount))\n        return candidates, idx\n\n    @classmethod\n    def show_devices(cls, scheme: str,\n                     vdict: Dict[str, int],\n                     pdict: Dict[int, Dict[str, int]],\n                     devdescs: Sequence[Tuple[UsbDeviceDescriptor, int]],\n                     out: Optional[TextIO] = None):\n        \"\"\"Show supported devices. When the joker url ``scheme://*/?`` is\n           specified as an URL, it generates a list of connected USB devices\n           that match the supported USB devices. It can be used to provide the\n           end-user with a list of valid URL schemes.\n\n           :param scheme: scheme to match in the URL string (scheme://...)\n           :param vdict: vendor name map of USB vendor ids\n           :param pdict: vendor id map of product name map of product ids\n           :param devdescs: candidate devices\n           :param out: output stream, none for stdout\n        \"\"\"\n        if not devdescs:\n            return\n        if not out:\n            out = sys.stdout\n        devstrs = cls.build_dev_strings(scheme, vdict, pdict, devdescs)\n        max_url_len = max(len(url) for url, _ in devstrs)\n        print('Available interfaces:', file=out)\n        for url, desc in devstrs:\n            print(f'  {url:{max_url_len}s}  {desc}', file=out)\n        print('', file=out)\n\n    @classmethod\n    def build_dev_strings(cls, scheme: str,\n                          vdict: Dict[str, int],\n                          pdict: Dict[int, Dict[str, int]],\n                          devdescs: Sequence[Tuple[UsbDeviceDescriptor,\n                                                   int]]) -> \\\n            List[Tuple[str, str]]:\n        \"\"\"Build URL and device descriptors from UsbDeviceDescriptors.\n\n           :param scheme: protocol part of the URLs to generate\n           :param vdict: vendor name map of USB vendor ids\n           :param pdict: vendor id map of product name map of product ids\n           :param devdescs: USB devices and interfaces\n           :return: list of (url, descriptors)\n        \"\"\"\n        indices = {}  # Dict[Tuple[int, int], int]\n        descs = []\n        for desc, ifcount in sorted(devdescs):\n            ikey = (desc.vid, desc.pid)\n            indices[ikey] = indices.get(ikey, 0) + 1\n            # try to find a matching string for the current vendor\n            vendors = []\n            # fallback if no matching string for the current vendor is found\n            vendor = f'{desc.vid:04x}'\n            for vidc in vdict:\n                if vdict[vidc] == desc.vid:\n                    vendors.append(vidc)\n            if vendors:\n                vendors.sort(key=len)\n                vendor = vendors[0]\n            # try to find a matching string for the current vendor\n            # fallback if no matching string for the current product is found\n            product = f'{desc.pid:04x}'\n            try:\n                products = []\n                productids = pdict[desc.vid]\n                for prdc in productids:\n                    if productids[prdc] == desc.pid:\n                        products.append(prdc)\n                if products:\n                    product = products[0]\n            except KeyError:\n                pass\n            for port in range(1, ifcount+1):\n                fmt = '%s://%s/%d'\n                parts = [vendor, product]\n                sernum = desc.sn\n                if not sernum:\n                    sernum = ''\n                if [c for c in sernum if c not in printablechars or c == '?']:\n                    serial = f'{indices[ikey]}'\n                else:\n                    serial = sernum\n                if serial:\n                    parts.append(serial)\n                elif desc.bus is not None and desc.address is not None:\n                    parts.append(f'{desc.bus:x}')\n                    parts.append(f'{desc.address:x}')\n                # the description may contain characters that cannot be\n                # emitted in the output stream encoding format\n                try:\n                    url = fmt % (scheme, ':'.join(parts), port)\n                except Exception:\n                    url = fmt % (scheme, ':'.join([vendor, product, '???']),\n                                 port)\n                try:\n                    if desc.description:\n                        description = f'({desc.description})'\n                    else:\n                        description = ''\n                except Exception:\n                    description = ''\n                descs.append((url, description))\n        return descs\n\n    @classmethod\n    def get_string(cls, device: UsbDevice, stridx: int) -> str:\n        \"\"\"Retrieve a string from the USB device, dealing with PyUSB API breaks\n\n           :param device: USB device instance\n           :param stridx: the string identifier\n           :return: the string read from the USB device\n        \"\"\"\n        if cls.UsbApi is None:\n            # pylint: disable=import-outside-toplevel\n            import inspect\n            args, _, _, _ = \\\n                inspect.signature(UsbDevice.read).parameters\n            if (len(args) >= 3) and args[1] == 'length':\n                cls.UsbApi = 1\n            else:\n                cls.UsbApi = 2\n        try:\n            if cls.UsbApi == 2:\n                return usb_get_string(device, stridx)\n            return usb_get_string(device, 64, stridx)\n        except UnicodeDecodeError:\n            # do not abort if EEPROM data is somewhat incoherent\n            return ''\n\n    @classmethod\n    def find_backend(cls) -> IBackend:\n        \"\"\"Try to find and load an PyUSB backend.\n\n           ..note:: There is no need to call this method for regular usage.\n\n           :return: PyUSB backend\n        \"\"\"\n        with cls.Lock:\n            return cls._load_backend()\n\n    @classmethod\n    def _find_devices(cls, vendor: int, product: int,\n                      nocache: bool = False) -> Set[UsbDevice]:\n        \"\"\"Find a USB device and return it.\n\n           This code re-implements the usb.core.find() method using a local\n           cache to avoid calling several times the underlying LibUSB and the\n           system USB calls to enumerate the available USB devices. As these\n           calls are time-hungry (about 1 second/call), the enumerated devices\n           are cached. It consumes a bit more memory but dramatically improves\n           start-up time.\n           Hopefully, this kludge is temporary and replaced with a better\n           implementation from PyUSB at some point.\n\n           :param vendor: USB vendor id\n           :param product: USB product id\n           :param bool nocache: bypass cache to re-enumerate USB devices on\n                                the host\n           :return: a set of USB device matching the vendor/product identifier\n                    pair\n        \"\"\"\n        backend = cls._load_backend()\n        vidpid = (vendor, product)\n        if nocache or (vidpid not in cls.UsbDevices):\n            # not freed until Python runtime completion\n            # enumerate_devices returns a generator, so back up the\n            # generated device into a list. To save memory, we only\n            # back up the supported devices\n            devs = set()\n            vpdict = {}  # Dict[int, List[int]]\n            vpdict.setdefault(vendor, [])\n            vpdict[vendor].append(product)\n            for dev in backend.enumerate_devices():\n                # pylint: disable=no-member\n                device = UsbDevice(dev, backend)\n                if device.idVendor in vpdict:\n                    products = vpdict[device.idVendor]\n                    if products and (device.idProduct not in products):\n                        continue\n                    devs.add(device)\n            if sys.platform == 'win32':\n                # ugly kludge for a boring OS:\n                # on Windows, the USB stack may enumerate the very same\n                # devices several times: a real device with N interface\n                # appears also as N device with as single interface.\n                # We only keep the \"device\" that declares the most\n                # interface count and discard the \"virtual\" ones.\n                filtered_devs = {}\n                for dev in devs:\n                    vid = dev.idVendor\n                    pid = dev.idProduct\n                    ifc = max(cfg.bNumInterfaces for cfg in dev)\n                    k = (vid, pid, dev.bus, dev.address)\n                    if k not in filtered_devs:\n                        filtered_devs[k] = dev\n                    else:\n                        fdev = filtered_devs[k]\n                        fifc = max(cfg.bNumInterfaces for cfg in fdev)\n                        if fifc < ifc:\n                            filtered_devs[k] = dev\n                devs = set(filtered_devs.values())\n            cls.UsbDevices[vidpid] = devs\n        return cls.UsbDevices[vidpid]\n\n    @classmethod\n    def _get_backend_device(cls, device: UsbDevice) -> Any:\n        \"\"\"Return the backend implementation of a device.\n\n           :param device: the UsbDevice (usb.core.Device)\n           :return: the implementation of any\n        \"\"\"\n        try:\n            # pylint: disable=protected-access\n            # need to access private member _ctx of PyUSB device\n            # (resource manager) until PyUSB #302 is addressed\n            return device._ctx.dev\n            # pylint: disable=protected-access\n        except AttributeError:\n            return None\n\n    @classmethod\n    def _load_backend(cls) -> IBackend:\n        backend = None  # Optional[IBackend]\n        for candidate in cls.BACKENDS:\n            mod = import_module(candidate)\n            backend = mod.get_backend()\n            if backend is not None:\n                return backend\n        raise ValueError('No backend available')\n"
  },
  {
    "path": "requirements.txt",
    "content": "pyusb>=1.0, != 1.2.0\npyserial >= 3.0\n"
  },
  {
    "path": "setup.cfg",
    "content": "[bdist_wheel]\n\n[metadata]\nlicense_files = pyftdi/doc/license.rst\n\n[project]\nlicense = \"BSD-3-Clause\"\n\n[build_sphinx]\nsource-dir = pyftdi/doc\nbuild-dir  = sphinx\nall_files  = 1\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2010-2025 Emmanuel Blot <emmanuel.blot@free.fr>\n# Copyright (c) 2010-2016 Neotion\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\n# pylint: disable=unused-variable\n# pylint: disable=missing-docstring\n# pylint: disable=broad-except\n\nfrom codecs import open as codec_open\nfrom os import close, getcwd, unlink, walk\nfrom os.path import abspath, dirname, join as joinpath, relpath\nfrom py_compile import compile as pycompile, PyCompileError\nfrom re import split as resplit, search as research\nfrom sys import stderr, exit as sysexit\nfrom tempfile import mkstemp\nfrom setuptools import Command, find_packages, setup\nfrom setuptools.command.build_py import build_py\n\n\nNAME = 'pyftdi'\nPACKAGES = find_packages(where='.')\nMETA_PATH = joinpath('pyftdi', '__init__.py')\nKEYWORDS = ['driver', 'ftdi', 'usb', 'serial', 'spi', 'i2c', 'twi', 'rs232',\n            'gpio', 'bit-bang']\nCLASSIFIERS = [\n    'Development Status :: 4 - Beta',\n    'Environment :: Other Environment',\n    'Natural Language :: English',\n    'Intended Audience :: Developers',\n    'Operating System :: MacOS :: MacOS X',\n    'Operating System :: POSIX',\n    'Programming Language :: Python :: 3.9',\n    'Programming Language :: Python :: 3.10',\n    'Programming Language :: Python :: 3.11',\n    'Programming Language :: Python :: 3.12',\n    'Programming Language :: Python :: 3.13',\n    'Topic :: Software Development :: Libraries :: Python Modules',\n    'Topic :: System :: Hardware :: Hardware Drivers',\n]\nINSTALL_REQUIRES = [\n    'pyusb >= 1.0.0, != 1.2.0',\n    'pyserial >= 3.0',\n]\n\nHERE = abspath(dirname(__file__))\n\n\ndef read(*parts):\n    \"\"\"\n    Build an absolute path from *parts* and and return the contents of the\n    resulting file.  Assume UTF-8 encoding.\n    \"\"\"\n    with codec_open(joinpath(HERE, *parts), 'rb', 'utf-8') as dfp:\n        return dfp.read()\n\n\ndef read_desc(*parts):\n    \"\"\"Read and filter long description\n    \"\"\"\n    text = read(*parts)\n    text = resplit(r'\\.\\.\\sEOT', text)[0]\n    return text\n\n\nMETA_FILE = read(META_PATH)\n\n\ndef find_meta(meta):\n    \"\"\"\n    Extract __*meta*__ from META_FILE.\n    \"\"\"\n    meta_match = research(rf\"(?m)^__{meta}__ = ['\\\"]([^'\\\"]*)['\\\"]\", META_FILE)\n    if meta_match:\n        return meta_match.group(1)\n    raise RuntimeError(f'Unable to find __{meta}__ string.')\n\n\nclass BuildPy(build_py):\n    \"\"\"Override byte-compile sequence to catch any syntax error issue.\n\n       For some reason, distutils' byte-compile when it forks a sub-process\n       to byte-compile a .py file into a .pyc does NOT check the success of\n       the compilation. Therefore, any syntax error is explictly ignored,\n       and no output file is generated. This ends up generating an incomplete\n       package w/ a nevertheless successfull setup.py execution.\n\n       Here, each Python file is build before invoking distutils, so that any\n       syntax error is catched, raised and setup.py actually fails should this\n       event arise.\n\n       This step is critical to check that an unsupported syntax does not end\n       up as a 'valid' package from setuptools perspective...\n    \"\"\"\n\n    def byte_compile(self, files):\n        for file in files:\n            if not file.endswith('.py'):\n                continue\n            pfd, pyc = mkstemp('.pyc')\n            close(pfd)\n            try:\n                pycompile(file, pyc, doraise=True)\n                continue\n            except PyCompileError as exc:\n                # avoid chaining exceptions\n                print(str(exc), file=stderr)\n                raise SyntaxError(f\"Cannot byte-compile '{file}'\") from exc\n            finally:\n                unlink(pyc)\n        super().byte_compile(files)\n\n\nclass CheckStyle(Command):\n    \"\"\"A custom command to check Python coding style.\"\"\"\n\n    description = 'check coding style'\n    user_options = []\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n    def run(self):\n        self.announce('checking coding style', level=2)\n        filecount = 0\n        topdir = dirname(__file__) or getcwd()\n        error_count = 0\n        for dpath, dnames, fnames in walk(topdir):\n            dnames[:] = [d for d in dnames\n                         if not d.startswith('.') and d != 'doc']\n            for filename in (joinpath(dpath, f)\n                             for f in fnames if f.endswith('.py')):\n                self.announce(f'checking {relpath(filename, topdir)}', level=2)\n                with open(filename, 'rt', encoding='utf-8') as pfp:\n                    for lpos, line in enumerate(pfp, start=1):\n                        if len(line) > 80:\n                            toppath = relpath(filename, topdir)\n                            print(f'  invalid line width in {toppath}:{lpos}',\n                                  file=stderr)\n                            print(f'    {line.strip()}', file=stderr)\n                            error_count += 1\n                filecount += 1\n        if error_count:\n            raise RuntimeError(f'{error_count} errors')\n        if not filecount:\n            raise RuntimeError(f'No Python file found from \"{topdir}\"')\n\n\ndef main():\n    setup(\n        cmdclass={\n            'build_py': BuildPy,\n            'check_style': CheckStyle\n        },\n        name=NAME,\n        description=find_meta('description'),\n        license=find_meta('license'),\n        url=find_meta('uri'),\n        version=find_meta('version'),\n        author=find_meta('author'),\n        author_email=find_meta('email'),\n        maintainer=find_meta('author'),\n        maintainer_email=find_meta('email'),\n        keywords=KEYWORDS,\n        long_description=read_desc('pyftdi/doc/index.rst'),\n        long_description_content_type='text/x-rst',\n        packages=PACKAGES,\n        scripts=['pyftdi/bin/i2cscan.py',\n                 'pyftdi/bin/ftdi_urls.py',\n                 'pyftdi/bin/ftconf.py',\n                 'pyftdi/bin/pyterm.py'],\n        package_dir={'': '.'},\n        package_data={'pyftdi': ['*.rst', 'doc/*.rst', 'doc/api/*.rst',\n                                 'INSTALL'],\n                      'pyftdi.serialext': ['*.rst', 'doc/api/uart.rst']},\n        classifiers=CLASSIFIERS,\n        install_requires=INSTALL_REQUIRES,\n        python_requires='>=3.9',\n    )\n\n\nif __name__ == '__main__':\n    try:\n        main()\n    except Exception as exc_:\n        print(exc_, file=stderr)\n        sysexit(1)\n"
  },
  {
    "path": "test-requirements.txt",
    "content": "setuptools\nwheel\npylint\nruamel.yaml >= 0.16\n"
  }
]