├── docs ├── source │ ├── modules.rst │ ├── index.rst │ ├── buspirate.rst │ └── conf.py └── build │ └── .gitignore ├── README.md ├── .editorconfig ├── tox.ini ├── src └── buspirate │ ├── __version__.py │ ├── __init__.py │ ├── openocd.py │ ├── onewire.py │ ├── utilities.py │ ├── i2c.py │ ├── uart.py │ ├── base.py │ ├── spi.py │ └── rawwire.py ├── tests ├── __init__.py ├── mocks │ └── serial.py ├── test_buspirate_openocd.py ├── test_buspirate_onewire.py ├── test_buspirate_uart.py ├── test_buspirate_spi.py ├── test_buspirate_utilities.py ├── test_buspirate_i2c.py ├── test_buspirate_base.py └── test_buspirate_rawwire.py ├── pyproject.toml ├── .gitignore ├── LICENSE └── poetry.lock /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | src 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | buspirate 8 | -------------------------------------------------------------------------------- /docs/build/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyBusPirate 2 | Library for the [Dangerous Prototypes Bus Pirate](http://dangerousprototypes.com/docs/Bus_Pirate). 3 | ## Installation 4 | 5 | ### Requirements 6 | * Linux 7 | * Python 3.5 and up 8 | 9 | ## Contributing 10 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 11 | 12 | Please make sure to update tests as appropriate. 13 | 14 | ## License 15 | [GPLv2](https://choosealicense.com/licenses/gpl-2.0/) 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # -*- mode: conf-unix; -*- 2 | 3 | # EditorConfig is awesome: http://EditorConfig.org 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # defaults 9 | [*] 10 | insert_final_newline = true 11 | 12 | # 4 space indentation 13 | [*.{ini,py,py.tpl,rst}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | # 4-width tabbed indentation 18 | [*.{sh,bat.tpl,Makefile.tpl}] 19 | indent_style = tab 20 | indent_size = 4 21 | 22 | # and travis does its own thing 23 | [.travis.yml] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pyBusPirate documentation master file, created by 2 | sphinx-quickstart on Sat Aug 18 18:28:28 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pyBusPirate's documentation! 7 | ======================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py3 4 | isolated_build = True 5 | 6 | [tox:.package] 7 | basepython = python3 8 | 9 | [testenv] 10 | commands = 11 | poetry install 12 | poetry run pytest 13 | whitelist_externals = poetry 14 | 15 | [pytest] 16 | testpaths = tests 17 | addopts = 18 | --verbose 19 | --cov=buspirate 20 | --cov-config=tox.ini 21 | --cov-report=term 22 | 23 | [coverage:run] 24 | omit = 25 | src/buspirate/__init__.py 26 | src/buspirate/__version__.py 27 | 28 | [coverage:paths] 29 | source = 30 | src/buspirate 31 | **/site-packages/buspirate 32 | 33 | [coverage:report] 34 | show_missing = True 35 | 36 | [MESSAGES CONTROL] 37 | disable = R0801 38 | -------------------------------------------------------------------------------- /src/buspirate/__version__.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ Main Version Info for pyBusPirate package """ 20 | __version__ = '2.0.0' 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Allows Auto Discovery of Unit Tests 21 | """ 22 | 23 | import sys 24 | sys.path.append('./src') 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pyBusPirate" 3 | version = "2.0.0" 4 | description = "Library for the Dangerous Prototypes Bus Pirate" 5 | license = "GPL-2.0-only" 6 | authors = ["Sean Nelson "] 7 | readme = "README.md" 8 | repository = "https://github.com/audiohacked/python-pyBusPirate" 9 | keywords = [ 10 | "BusPirate" 11 | ] 12 | classifiers = [ 13 | "Intended Audience :: Developers", 14 | "Operating System :: OS Independent", 15 | "Programming Language :: Python :: 3", 16 | "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", 17 | "Topic :: Software Development :: Embedded Systems", 18 | "Topic :: System :: Hardware", 19 | "Topic :: System :: Hardware :: Hardware Drivers", 20 | ] 21 | packages = [ 22 | { include = "buspirate", from="src" } 23 | ] 24 | 25 | [tool.poetry.dependencies] 26 | python = "^3.6.2" 27 | pyserial = "^3.5" 28 | 29 | [tool.poetry.dev-dependencies] 30 | pytest = "^6.2.4" 31 | coverage = "^5.5" 32 | pytest-cov = "^2.12.1" 33 | tox = "^3.23.1" 34 | pylint = "^2.9.3" 35 | 36 | [build-system] 37 | requires = ["poetry-core>=1.0.0"] 38 | build-backend = "poetry.core.masonry.api" 39 | -------------------------------------------------------------------------------- /tests/mocks/serial.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Mock Device for Serial Devices 21 | """ 22 | 23 | from unittest import mock 24 | 25 | class MockSerial(mock.MagicMock): 26 | def __init__(self, *args, **kwargs): 27 | pass 28 | 29 | def open(self): 30 | pass 31 | 32 | def read(self, count): 33 | pass 34 | 35 | def write(self, data): 36 | pass -------------------------------------------------------------------------------- /src/buspirate/__init__.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Main Entry for pyBusPirate package 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | from buspirate.__version__ import __version__ 26 | 27 | __license__ = "GPL" 28 | __status__ = "Development" 29 | 30 | __author__ = "Sean Nelson" 31 | __authoremail__ = "audiohacked@gmail.com" 32 | 33 | __copyright__ = "Copyright 2018, Sean Nelson " 34 | 35 | __maintainer__ = "Sean Nelson" 36 | __email__ = "audiohacked@gmail.com" 37 | -------------------------------------------------------------------------------- /src/buspirate/openocd.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ OpenOCD JTAG class """ 20 | 21 | from buspirate.base import BusPirate 22 | 23 | 24 | class JTAG(BusPirate): 25 | """ JTAG BitBanging on the BusPirate """ 26 | @property 27 | def exit(self): 28 | """ 29 | Exit JTAG Mode on the BusPirate 30 | 31 | :returns: returns Success or Failure 32 | """ 33 | self.serial.write(0x00) 34 | return self.read(5) == "BBIO1" 35 | 36 | @property 37 | def enter(self): 38 | """ 39 | Enter JTAG Mode on the BusPirate 40 | 41 | :returns: returns Success or Failure 42 | """ 43 | self.write(0x06) 44 | return self.read(4) == "1W01" 45 | 46 | 47 | if __name__ == '__main__': 48 | pass 49 | -------------------------------------------------------------------------------- /docs/source/buspirate.rst: -------------------------------------------------------------------------------- 1 | buspirate package 2 | ================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | buspirate.base module 8 | --------------------- 9 | 10 | .. automodule:: buspirate.base 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | buspirate.i2c module 16 | -------------------- 17 | 18 | .. automodule:: buspirate.i2c 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | buspirate.onewire module 24 | ------------------------ 25 | 26 | .. automodule:: buspirate.onewire 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | buspirate.openocd module 32 | ------------------------ 33 | 34 | .. automodule:: buspirate.openocd 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | buspirate.rawwire module 40 | ------------------------ 41 | 42 | .. automodule:: buspirate.rawwire 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | buspirate.spi module 48 | -------------------- 49 | 50 | .. automodule:: buspirate.spi 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | buspirate.uart module 56 | --------------------- 57 | 58 | .. automodule:: buspirate.uart 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | buspirate.utilities module 64 | -------------------------- 65 | 66 | .. automodule:: buspirate.utilities 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | 72 | Module contents 73 | --------------- 74 | 75 | .. automodule:: buspirate 76 | :members: 77 | :undoc-members: 78 | :show-inheritance: 79 | -------------------------------------------------------------------------------- /tests/test_buspirate_openocd.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate SPI class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate import openocd 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateOpenOCDTest(unittest.TestCase): 30 | @mock.patch('serial.Serial', autospec=True) 31 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 32 | self.bus_pirate = openocd.JTAG("/dev/ttyUSB0") 33 | 34 | def tearDown(self): 35 | pass 36 | 37 | def test_exit(self): 38 | self.bus_pirate.serial.read.return_value = "BBIO1" 39 | self.assertEqual(self.bus_pirate.exit, True) 40 | self.bus_pirate.serial.write.assert_called_with(0x00) 41 | 42 | def test_mode(self): 43 | self.bus_pirate.serial.read.return_value = "1W01" 44 | self.assertEqual(self.bus_pirate.mode, "1W01") 45 | self.bus_pirate.serial.write.assert_called_with(0x01) 46 | 47 | def test_enter(self): 48 | self.bus_pirate.serial.read.return_value = "1W01" 49 | self.assertEqual(self.bus_pirate.enter, True) 50 | self.bus_pirate.serial.write.assert_called_with(0x06) 51 | -------------------------------------------------------------------------------- /src/buspirate/onewire.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 1Wire Base class """ 20 | 21 | from buspirate.base import BusPirate 22 | 23 | 24 | class OneWire(BusPirate): 25 | """ OneWire BitBanging on the BusPirate """ 26 | @property 27 | def exit(self): 28 | """ 29 | Exit OneWire Mode on the BusPirate 30 | 31 | :returns: returns Success or Failure 32 | """ 33 | self.serial.write(0x00) 34 | return self.read(5) == "BBIO1" 35 | 36 | @property 37 | def enter(self): 38 | """ 39 | Enter OneWire Mode on the BusPirate 40 | 41 | :returns: returns Success or Failure 42 | """ 43 | self.serial.write(0x04) 44 | return self.read(4) == "1W01" 45 | 46 | def read_byte(self): 47 | """ 48 | Read 1 Byte from Interface 49 | 50 | :returns: returns Success or Failure 51 | """ 52 | self.serial.write(0x04) 53 | return self.read(1) 54 | 55 | @property 56 | def rom_search(self): 57 | """ 58 | Search for ROMs, successive reads are device address, terminates with 8 0xff 59 | 60 | :returns: returns Success or Failure 61 | """ 62 | self.serial.write(0x08) 63 | return self.read(1) == 0x01 64 | 65 | @property 66 | def alarm_search(self): 67 | """ 68 | Search for Alarms, successive reads are device address, terminates with 8 0xff 69 | 70 | :returns: returns Success or Failure 71 | """ 72 | self.serial.write(0x09) 73 | return self.read(1) == 0x01 74 | 75 | def pullup_voltage_select(self) -> None: 76 | """ 77 | Select Pull-Up Voltage 78 | 79 | Unimplmented! 80 | """ 81 | raise NotImplementedError 82 | 83 | 84 | if __name__ == '__main__': 85 | pass 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | -------------------------------------------------------------------------------- /tests/test_buspirate_onewire.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate SPI class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate import onewire 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateOneWireTest(unittest.TestCase): 30 | @mock.patch('serial.Serial', autospec=True) 31 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 32 | self.bus_pirate = onewire.OneWire("/dev/ttyUSB0") 33 | 34 | def tearDown(self): 35 | pass 36 | 37 | def test_exit(self): 38 | self.bus_pirate.serial.read.return_value = "BBIO1" 39 | self.assertEqual(self.bus_pirate.exit, True) 40 | self.bus_pirate.serial.write.assert_called_with(0x00) 41 | 42 | def test_mode(self): 43 | self.bus_pirate.serial.read.return_value = "1W01" 44 | self.assertEqual(self.bus_pirate.mode, "1W01") 45 | self.bus_pirate.serial.write.assert_called_with(0x01) 46 | 47 | def test_enter(self): 48 | self.bus_pirate.serial.read.return_value = "1W01" 49 | self.assertEqual(self.bus_pirate.enter, True) 50 | self.bus_pirate.serial.write.assert_called_with(0x04) 51 | 52 | def test_read_byte(self) -> bytes: 53 | self.bus_pirate.serial.read.side_effect = [0x01, 0xFF] 54 | self.assertEqual(self.bus_pirate.read_byte(), True) 55 | self.bus_pirate.serial.write.assert_called_with(0x04) 56 | 57 | def test_rom_search(self): 58 | self.bus_pirate.serial.read.return_value = 0x01 59 | self.assertEqual(self.bus_pirate.rom_search, True) 60 | self.bus_pirate.serial.write.assert_called_with(0x08) 61 | 62 | def test_alarm_search(self): 63 | self.bus_pirate.serial.read.return_value = 0x01 64 | self.assertEqual(self.bus_pirate.alarm_search, True) 65 | self.bus_pirate.serial.write.assert_called_with(0x09) 66 | 67 | def test_1wire_bulk_write(self): 68 | read_data = [0x00 for idx in range(1, 17)] 69 | write_data = [idx for idx in range(1, 17)] 70 | self.bus_pirate.serial.read.side_effect = [0x01, read_data] 71 | result = self.bus_pirate.bulk_write(16, write_data) 72 | self.assertEqual(result, read_data) 73 | self.bus_pirate.serial.write.assert_any_call(0x1F) 74 | self.bus_pirate.serial.write.assert_any_call(write_data) 75 | 76 | def test_pullup_voltage_select(self): 77 | with self.assertRaises(NotImplementedError): 78 | self.bus_pirate.pullup_voltage_select() 79 | -------------------------------------------------------------------------------- /src/buspirate/utilities.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ pyBusPirate Utilities """ 20 | 21 | from buspirate.base import BusPirate 22 | 23 | 24 | class Voltage(BusPirate): 25 | """ Voltage BitBang """ 26 | def take_once(self): 27 | """ 28 | Voltage Take Once 29 | 30 | :returns: returns Success or Failure 31 | :rtype: bool. 32 | """ 33 | self.write(0x14) 34 | return self.read(1) == 0x01 35 | 36 | def continuous(self): 37 | """ 38 | Voltage Continuous 39 | 40 | :returns: returns Success or Failure 41 | :rtype: bool. 42 | """ 43 | self.write(0x15) 44 | return self.read(1) == 0x01 45 | 46 | 47 | class SelfTests(BusPirate): 48 | """ Self-Tests on BusPirate """ 49 | def short_test(self): 50 | """ 51 | Short Self-Tests 52 | 53 | :returns: returns nothing 54 | """ 55 | self.write(0x10) 56 | 57 | def long_test(self): 58 | """ 59 | Short Self-Tests 60 | 61 | :returns: returns nothing 62 | """ 63 | self.write(0x11) 64 | 65 | def exit(self): 66 | """ 67 | Exit from Self-Tests 68 | 69 | :returns: returns Success or Failure 70 | :rtype: bool. 71 | """ 72 | self.write(0xff) 73 | return self.read(1) == 0x01 74 | 75 | 76 | class PWM(BusPirate): 77 | """ PWM BitBang """ 78 | def setup(self): 79 | """ 80 | PWM Setup 81 | 82 | :returns: returns Success or Failure 83 | :rtype: bool. 84 | """ 85 | self.write(0x12) 86 | return self.read(1) == 0x01 87 | 88 | def clear(self): 89 | """ 90 | PWM Clear 91 | 92 | :returns: returns Success or Failure 93 | :rtype: bool. 94 | """ 95 | self.write(0x13) 96 | return self.read(1) == 0x01 97 | 98 | def disable(self): 99 | """ 100 | PWM Disable 101 | 102 | :returns: returns Success or Failure 103 | :rtype: bool. 104 | """ 105 | self.write(0x13) 106 | return self.read(1) == 0x01 107 | 108 | 109 | class Frequency(BusPirate): 110 | """ Frequency Measurements on BusPirate """ 111 | def measure(self): 112 | """ 113 | Frequency Disable 114 | 115 | :returns: returns Success or Failure 116 | :rtype: bool. 117 | """ 118 | self.write(0x16) 119 | return self.read(1) == 0x01 120 | 121 | 122 | if __name__ == '__main__': 123 | pass 124 | -------------------------------------------------------------------------------- /tests/test_buspirate_uart.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate SPI class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate import uart 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateUartTest(unittest.TestCase): 30 | """ Unit Test class """ 31 | @mock.patch('serial.Serial', autospec=True) 32 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 33 | """ Unit Test setup """ 34 | self.bus_pirate = uart.UART("/dev/ttyUSB0") 35 | 36 | def tearDown(self): 37 | """ Unit Test cleanup """ 38 | pass 39 | 40 | def test_enter(self): 41 | self.bus_pirate.serial.read.return_value = "ART1" 42 | self.assertEqual(self.bus_pirate.enter, True) 43 | self.bus_pirate.serial.write.assert_called_with(0x03) 44 | 45 | def test_mode(self): 46 | self.bus_pirate.serial.read.return_value = "ART1" 47 | self.assertEqual(self.bus_pirate.mode, "ART1") 48 | self.bus_pirate.serial.write.assert_called_with(0x01) 49 | 50 | def test_echo_rx(self): 51 | start = 0b0 52 | self.bus_pirate.serial.read.return_value = 0x01 53 | self.assertEqual(self.bus_pirate.echo_rx(start), True) 54 | self.bus_pirate.serial.write.assert_called_with(0x02) 55 | 56 | def test_manual_baudrate(self): 57 | self.bus_pirate.serial.read.return_value = [0x01, 0x01, 0x01] 58 | self.assertEqual(self.bus_pirate.manual_baudrate(0x0000), True) 59 | self.bus_pirate.serial.write.assert_called_with([0x07, 0x0000]) 60 | 61 | def test_bridge_mode(self): 62 | self.bus_pirate.serial.read.return_value = 0x01 63 | self.assertEqual(self.bus_pirate.bridge_mode, True) 64 | self.bus_pirate.serial.write.assert_called_with(0x0F) 65 | 66 | def test_uart_bulk_write(self): 67 | data = [idx for idx in range(1, 17)] 68 | self.bus_pirate.serial.read.side_effect = [0x01, data] 69 | result = self.bus_pirate.bulk_write(16, data) 70 | self.assertEqual(result, data) 71 | self.bus_pirate.serial.write.assert_any_call(0x10|0x0F) 72 | self.bus_pirate.serial.write.assert_any_call(data) 73 | 74 | def test_uart_speed(self): 75 | self.bus_pirate.speed = uart.UartSpeed.BAUD_115200 76 | self.bus_pirate.serial.read.return_value = 0x01 77 | self.assertEqual(self.bus_pirate.speed, uart.UartSpeed.BAUD_115200) 78 | self.bus_pirate.serial.write.assert_called_with(0x60|0x0A) 79 | 80 | def test_uart_config(self): 81 | self.bus_pirate.config = 0b000 82 | self.bus_pirate.serial.read.return_value = 0x01 83 | self.assertEqual(self.bus_pirate.config, 0b0000) 84 | self.bus_pirate.serial.write.assert_called_with(0x80|0x00) 85 | -------------------------------------------------------------------------------- /tests/test_buspirate_spi.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate SPI class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate import spi 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateSpiTest(unittest.TestCase): 30 | """ Unit Test class """ 31 | @mock.patch('serial.Serial', autospec=True) 32 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 33 | """ Unit Test setup """ 34 | self.bus_pirate = spi.SPI("/dev/ttyUSB0") 35 | 36 | def tearDown(self): 37 | """ Unit Test cleanup """ 38 | pass 39 | 40 | def test_enter(self): 41 | self.bus_pirate.serial.read.return_value = "SPI1" 42 | self.assertEqual(self.bus_pirate.enter, True) 43 | self.bus_pirate.serial.write.assert_called_with(0x01) 44 | 45 | def test_chip_select_high(self): 46 | self.bus_pirate.serial.read.return_value = 0x01 47 | self.assertEqual(self.bus_pirate.chip_select(spi.CsLevel.HIGH), True) 48 | self.bus_pirate.serial.write.assert_called_with(0x02|0x01) 49 | 50 | def test_chip_select_low(self): 51 | self.bus_pirate.serial.read.return_value = 0x01 52 | self.assertEqual(self.bus_pirate.chip_select(spi.CsLevel.LOW), True) 53 | self.bus_pirate.serial.write.assert_called_with(0x02|0x00) 54 | 55 | def test_cs(self): 56 | self.bus_pirate.serial.read.return_value = 0x01 57 | self.bus_pirate.cs = spi.CsLevel.LOW 58 | self.assertEqual(self.bus_pirate.cs, spi.CsLevel.LOW) 59 | self.bus_pirate.serial.write.assert_called_with(0x02|0x00) 60 | 61 | def test_sniff(self): 62 | self.bus_pirate.serial.read.return_value = 0x01 63 | self.assertEqual(self.bus_pirate.sniff(spi.CsSniffTrigger.LOW), True) 64 | self.bus_pirate.serial.write.assert_called_with(0x0C|0x02) 65 | 66 | def test_spi_speed(self): 67 | self.bus_pirate.serial.read.return_value = 0x01 68 | self.bus_pirate.speed = spi.SpiSpeed.SPEED_8MHZ 69 | self.assertEqual(self.bus_pirate.speed, 0b111) 70 | self.bus_pirate.serial.write.assert_called_with(0x60|0x07) 71 | 72 | def test_spi_config(self): 73 | self.bus_pirate.serial.read.return_value = 0x01 74 | self.bus_pirate.config = 0b0000 75 | self.assertEqual(self.bus_pirate.config, 0b0000) 76 | self.bus_pirate.serial.write.assert_called_with(0x80|0x00) 77 | 78 | def test_write_then_read(self): 79 | data_len = 128 80 | data = [idx for idx in range(1, data_len+1)] 81 | self.bus_pirate.serial.read.return_value = [0x01] + data 82 | result = self.bus_pirate.write_then_read(data_len, data_len, data) 83 | self.assertEqual(result, data) 84 | self.bus_pirate.serial.write.assert_called_with([0x04, 128, 128, data]) 85 | 86 | def test_write_then_read_with_no_cs(self): 87 | data_len = 128 88 | data = [idx for idx in range(1, data_len+1)] 89 | self.bus_pirate.serial.read.return_value = [0x01] + data 90 | result = self.bus_pirate.write_then_read_with_no_cs(data_len, data_len, data) 91 | self.assertEqual(result, data) 92 | self.bus_pirate.serial.write.assert_called_with([0x05, 128, 128, data]) 93 | -------------------------------------------------------------------------------- /tests/test_buspirate_utilities.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate base class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate.utilities import Voltage, SelfTests, PWM, Frequency 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateVoltageTest(unittest.TestCase): 30 | """ Unit Test class """ 31 | @mock.patch('serial.Serial', autospec=True) 32 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 33 | """ Unit Test setup """ 34 | self.bus_pirate = Voltage("/dev/ttyUSB0") 35 | 36 | def tearDown(self): 37 | """ Unit Test cleanup """ 38 | pass 39 | 40 | def test_take_once(self): 41 | self.bus_pirate.serial.read.return_value = 0x01 42 | self.assertEqual(self.bus_pirate.take_once(), True) 43 | self.bus_pirate.serial.write.assert_called_with(0x14) 44 | 45 | def test_continuous(self): 46 | self.bus_pirate.serial.read.return_value = 0x01 47 | self.assertEqual(self.bus_pirate.continuous(), True) 48 | self.bus_pirate.serial.write.assert_called_with(0x15) 49 | 50 | 51 | class BusPirateSelfTestsTest(unittest.TestCase): 52 | """ Unit Test class """ 53 | @mock.patch('serial.Serial', autospec=True) 54 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 55 | """ Unit Test setup """ 56 | self.bus_pirate = SelfTests("/dev/ttyUSB0") 57 | 58 | def tearDown(self): 59 | """ Unit Test cleanup """ 60 | pass 61 | 62 | def test_short_test(self): 63 | self.assertEqual(self.bus_pirate.short_test(), None) 64 | self.bus_pirate.serial.write.assert_called_with(0x10) 65 | 66 | def test_long_test(self): 67 | self.assertEqual(self.bus_pirate.long_test(), None) 68 | self.bus_pirate.serial.write.assert_called_with(0x11) 69 | 70 | def test_exit(self): 71 | self.bus_pirate.serial.read.return_value = 0x01 72 | self.assertEqual(self.bus_pirate.exit(), True) 73 | self.bus_pirate.serial.write.assert_called_with(0xFF) 74 | 75 | 76 | class BusPiratePWMTest(unittest.TestCase): 77 | """ Unit Test class """ 78 | @mock.patch('serial.Serial', autospec=True) 79 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 80 | """ Unit Test setup """ 81 | self.bus_pirate = PWM("/dev/ttyUSB0") 82 | 83 | def tearDown(self): 84 | """ Unit Test cleanup """ 85 | pass 86 | 87 | def test_setup(self): 88 | self.bus_pirate.serial.read.return_value = 0x01 89 | self.assertEqual(self.bus_pirate.setup(), True) 90 | self.bus_pirate.serial.write.assert_called_with(0x12) 91 | 92 | def test_clear(self): 93 | self.bus_pirate.serial.read.return_value = 0x01 94 | self.assertEqual(self.bus_pirate.clear(), True) 95 | self.bus_pirate.serial.write.assert_called_with(0x13) 96 | 97 | def test_disable(self): 98 | self.bus_pirate.serial.read.return_value = 0x01 99 | self.assertEqual(self.bus_pirate.disable(), True) 100 | self.bus_pirate.serial.write.assert_called_with(0x13) 101 | 102 | 103 | class BusPirateFrequencyTest(unittest.TestCase): 104 | """ Unit Test class """ 105 | @mock.patch('serial.Serial', autospec=True) 106 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 107 | """ Unit Test setup """ 108 | self.bus_pirate = Frequency("/dev/ttyUSB0") 109 | 110 | def tearDown(self): 111 | """ Unit Test cleanup """ 112 | pass 113 | 114 | def test_measure(self): 115 | self.bus_pirate.serial.read.return_value = 0x01 116 | self.assertEqual(self.bus_pirate.measure(), True) 117 | self.bus_pirate.serial.write.assert_called_with(0x16) 118 | -------------------------------------------------------------------------------- /tests/test_buspirate_i2c.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate SPI class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate import i2c 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateI2CTest(unittest.TestCase): 30 | @mock.patch('serial.Serial', autospec=True) 31 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 32 | self.bus_pirate = i2c.I2C("/dev/ttyUSB0") 33 | 34 | def tearDown(self): 35 | pass 36 | 37 | def test_exit(self): 38 | self.bus_pirate.serial.read.return_value = "BBIO1" 39 | self.assertEqual(self.bus_pirate.exit, True) 40 | self.bus_pirate.serial.write.assert_called_with(0x00) 41 | 42 | def test_mode(self): 43 | self.bus_pirate.serial.read.return_value = "I2C1" 44 | self.assertEqual(self.bus_pirate.mode, True) 45 | self.bus_pirate.serial.write.assert_called_with(0x01) 46 | 47 | def test_enter(self): 48 | self.bus_pirate.serial.read.return_value = "I2C1" 49 | self.assertEqual(self.bus_pirate.enter, True) 50 | self.bus_pirate.serial.write.assert_called_with(0x02) 51 | 52 | def test_start_bit(self): 53 | self.bus_pirate.serial.read.return_value = 0x01 54 | self.assertEqual(self.bus_pirate.start_bit, True) 55 | self.bus_pirate.serial.write.assert_called_with(0x02) 56 | 57 | def test_stop_bit(self): 58 | self.bus_pirate.serial.read.return_value = 0x01 59 | self.assertEqual(self.bus_pirate.stop_bit, True) 60 | self.bus_pirate.serial.write.assert_called_with(0x03) 61 | 62 | def test_read_byte(self) -> bytes: 63 | self.bus_pirate.serial.read.side_effect = [0x01, 0xFF] 64 | self.assertEqual(self.bus_pirate.read_byte(), True) 65 | self.bus_pirate.serial.write.assert_called_with(0x04) 66 | 67 | def test_ack_bit(self): 68 | self.bus_pirate.serial.read.return_value = 0x01 69 | self.assertEqual(self.bus_pirate.ack_bit, True) 70 | self.bus_pirate.serial.write.assert_called_with(0x06) 71 | 72 | def test_nack_bit(self): 73 | self.bus_pirate.serial.read.return_value = 0x01 74 | self.assertEqual(self.bus_pirate.nack_bit, True) 75 | self.bus_pirate.serial.write.assert_called_with(0x07) 76 | 77 | def test_sniff(self): 78 | self.bus_pirate.serial.read.return_value = 0x01 79 | self.assertEqual(self.bus_pirate.sniff(), None) 80 | self.bus_pirate.serial.write.assert_called_with(0x0f) 81 | 82 | def test_i2c_bulk_write(self): 83 | read_data = [0x00 for idx in range(1, 17)] 84 | write_data = [idx for idx in range(1, 17)] 85 | self.bus_pirate.serial.read.side_effect = [0x01, read_data] 86 | result = self.bus_pirate.bulk_write(16, write_data) 87 | self.assertEqual(result, read_data) 88 | self.bus_pirate.serial.write.assert_any_call(0x1F) 89 | self.bus_pirate.serial.write.assert_any_call(write_data) 90 | 91 | def test_pullup_voltage_select(self): 92 | with self.assertRaises(NotImplementedError): 93 | self.bus_pirate.pullup_voltage_select() 94 | 95 | def test_i2c_speed(self): 96 | self.bus_pirate.speed = i2c.I2CSpeed.SPEED_50KHZ 97 | self.bus_pirate.serial.read.return_value = 0x01 98 | self.assertEqual(self.bus_pirate.speed, i2c.I2CSpeed.SPEED_50KHZ) 99 | self.bus_pirate.serial.write.assert_called_with(0x60|0x01) 100 | 101 | def test_write_then_read(self): 102 | data_len = 128 103 | data = [idx for idx in range(1, data_len+1)] 104 | self.bus_pirate.serial.read.return_value = [0x01] + data 105 | result = self.bus_pirate.write_then_read(data_len, data_len, data) 106 | self.assertEqual(result, data) 107 | self.bus_pirate.serial.write.assert_called_with([0x08, 128, 128, data]) 108 | 109 | def test_extend_aux(self): 110 | self.bus_pirate.serial.read.return_value = 0x01 111 | self.assertEqual(self.bus_pirate.extend_aux(i2c.I2CExtendAux.LOW), True) 112 | self.bus_pirate.serial.write.assert_called_with([0x09, 0x00]) 113 | -------------------------------------------------------------------------------- /tests/test_buspirate_base.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate base class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate.base import BusPirate 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateTest(unittest.TestCase): 30 | """ Unit Test class """ 31 | @mock.patch('serial.Serial', autospec=True) 32 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 33 | """ Unit Test setup """ 34 | self.bus_pirate = BusPirate("/dev/ttyUSB0") 35 | 36 | def tearDown(self): 37 | """ Unit Test cleanup """ 38 | pass 39 | 40 | def test_enter(self): 41 | self.bus_pirate.serial.read.return_value = "BBIO1" 42 | self.assertEqual(self.bus_pirate.enter, True) 43 | self.bus_pirate.serial.write.assert_called_with(0x00) 44 | 45 | def test_mode(self): 46 | self.bus_pirate.serial.read.return_value = "SPI1" 47 | self.assertEqual(self.bus_pirate.mode, "SPI1") 48 | self.bus_pirate.serial.write.assert_called_with(0x01) 49 | 50 | def test_reset(self): 51 | self.assertEqual(self.bus_pirate.reset, None) 52 | self.bus_pirate.serial.write.assert_called_with(0x0F) 53 | 54 | def test_configure_pins(self): 55 | with self.assertRaises(NotImplementedError): 56 | self.bus_pirate.configure_pins() 57 | 58 | def test_set_pins(self): 59 | with self.assertRaises(NotImplementedError): 60 | self.bus_pirate.set_pins() 61 | 62 | def test_configure_peripherals(self): 63 | self.bus_pirate.peripherals = 0b0000 64 | self.bus_pirate.serial.read.return_value = 0x01 65 | self.assertEqual(self.bus_pirate.peripherals, 0b0000) 66 | self.bus_pirate.serial.write.assert_called_with(0x40|0x00) 67 | 68 | def test_bulk_write(self): 69 | data = [idx for idx in range(1, 17)] 70 | self.bus_pirate.serial.read.side_effect = [0x01, data] 71 | result = self.bus_pirate.bulk_write(16, data) 72 | self.assertEqual(result, data) 73 | self.bus_pirate.serial.write.assert_any_call(0x1F) 74 | self.bus_pirate.serial.write.assert_any_call(data) 75 | 76 | def test_bulk_write_fail(self): 77 | data = [idx for idx in range(1, 17)] 78 | self.bus_pirate.serial.read.side_effect = [0x00, data] 79 | result = self.bus_pirate.bulk_write(16, data) 80 | self.assertEqual(result, bytes()) 81 | self.bus_pirate.serial.write.assert_called_with(0x1F) 82 | 83 | def test_bulk_write_data_is_none(self): 84 | data = [idx for idx in range(1, 17)] 85 | self.bus_pirate.serial.read.side_effect = [0x01, data] 86 | result = self.bus_pirate.bulk_write(16, None) 87 | self.assertEqual(result, data) 88 | self.bus_pirate.serial.write.assert_any_call(0x1F) 89 | self.bus_pirate.serial.write.assert_any_call(bytes(16)) 90 | 91 | def test_bulk_write_count_is_too_small(self): 92 | with self.assertRaises(ValueError): 93 | data = [idx for idx in range(1, 17)] 94 | result = self.bus_pirate.bulk_write(0, data) 95 | 96 | def test_bulk_write_count_is_too_big(self): 97 | with self.assertRaises(ValueError): 98 | data = [idx for idx in range(1, 17)] 99 | result = self.bus_pirate.bulk_write(17, data) 100 | 101 | def test__write_then_read(self): 102 | data = [idx for idx in range(1, 17)] 103 | self.bus_pirate.serial.read.return_value = [0x01] + data 104 | result = self.bus_pirate._write_then_read(command=0x04, write_count=16, 105 | read_count=len(data), write_data=data) 106 | self.assertEqual(result, data) 107 | self.bus_pirate.serial.write.assert_called_with([0x04, 16, len(data), data]) 108 | 109 | def test__write_then_read_fail(self): 110 | data = [idx for idx in range(1, 17)] 111 | self.bus_pirate.serial.read.return_value = [0x00] + data 112 | result = self.bus_pirate._write_then_read(command=0x04, write_count=16, 113 | read_count=len(data), write_data=data) 114 | self.assertEqual(result, bytes()) 115 | self.bus_pirate.serial.write.assert_called_with([0x04, 16, len(data), data]) 116 | -------------------------------------------------------------------------------- /src/buspirate/i2c.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ I2C class """ 20 | 21 | from enum import IntEnum 22 | 23 | from buspirate.base import BusPirate 24 | 25 | 26 | class I2CSpeed(IntEnum): 27 | """ Enum for I2C Speeds """ 28 | SPEED_5KHZ = 0b000 29 | SPEED_50KHZ = 0b001 30 | SPEED_100KHZ = 0b010 31 | SPEED_400KHZ = 0b011 32 | 33 | 34 | class I2CExtendAux(IntEnum): 35 | """ Enum for I2C Extend Aux """ 36 | LOW = 0x00 37 | HIGH = 0x01 38 | HIZ = 0x02 39 | AUX_READ = 0x03 40 | USE_AUX = 0x10 41 | USE_CS = 0x20 42 | 43 | 44 | class I2C(BusPirate): 45 | """ I2C BitBanging on the BusPirate """ 46 | @property 47 | def exit(self) -> bool: 48 | """ 49 | Exit to BitBang mode 50 | 51 | :returns: returns Success or Failure 52 | :rtype: bool 53 | """ 54 | self.write(0x00) 55 | return self.read(5) == "BBIO1" 56 | 57 | @property 58 | def mode(self) -> bool: 59 | """ 60 | Get Version and Mode 61 | 62 | :returns: returns Success or Failure 63 | :rtype: bool 64 | """ 65 | self.write(0x01) 66 | return self.read(4) == "I2C1" 67 | 68 | @property 69 | def enter(self) -> bool: 70 | """ 71 | Enter I2C Mode on the BusPirate 72 | 73 | :returns: returns Success or Failure 74 | :rtype: bool 75 | """ 76 | self.write(0x02) 77 | return self.read(4) == "I2C1" 78 | 79 | @property 80 | def start_bit(self) -> bool: 81 | """ 82 | Send I2C Start bit 83 | 84 | :returns: returns Success or Failure 85 | :rtype: bool 86 | """ 87 | self.write(0x02) 88 | return self.read(1) == 0x01 89 | 90 | @property 91 | def stop_bit(self) -> bool: 92 | """ 93 | Send I2C Stop bit 94 | 95 | :returns: returns Success or Failure 96 | :rtype: bool 97 | """ 98 | self.write(0x03) 99 | return self.read(1) == 0x01 100 | 101 | def read_byte(self) -> bytes: 102 | """ 103 | Read I2C Byte 104 | 105 | :returns: returns Success or Failure 106 | :rtype: bool 107 | """ 108 | self.write(0x04) 109 | return self.read(1) == 0x01 110 | 111 | @property 112 | def ack_bit(self) -> bool: 113 | """ 114 | Send I2C ACK bit 115 | 116 | :returns: returns Success or Failure 117 | :rtype: bool 118 | """ 119 | self.write(0x06) 120 | return self.read(1) == 0x01 121 | 122 | @property 123 | def nack_bit(self) -> bool: 124 | """ 125 | Send I2C NACK bit 126 | 127 | :returns: returns Success or Failure 128 | :rtype: bool 129 | """ 130 | self.write(0x07) 131 | return self.read(1) == 0x01 132 | 133 | def sniff(self) -> bool: 134 | """ 135 | Sniff I2C Bus 136 | 137 | :returns: returns nothing 138 | """ 139 | self.write(0x0F) 140 | 141 | def pullup_voltage_select(self) -> None: 142 | """ 143 | Select Pull-Up Voltage 144 | 145 | Unimplmented! 146 | """ 147 | raise NotImplementedError 148 | 149 | @property 150 | def speed(self): 151 | """ Speed Property Getter """ 152 | return self._speed 153 | 154 | @speed.setter 155 | def speed(self, value): 156 | """ Speed Property Setter """ 157 | self._speed = value 158 | return self.i2c_speed(value) 159 | 160 | def i2c_speed(self, i2c_speed: int = I2CSpeed.SPEED_5KHZ): 161 | """ 162 | SPI Speed Configuration 163 | 164 | :param i2c_speed: The SPI Clock Rate 165 | :type i2c_speed: int. 166 | 167 | :returns: returns Success or Failure 168 | :rtype: bool. 169 | """ 170 | self.write(0x60 | i2c_speed) 171 | return self.read(1) == 0x01 172 | 173 | def write_then_read(self, 174 | write_count: int = 0, 175 | read_count: int = 0, 176 | write_data: bytes = None) -> bytes: 177 | """ 178 | I2C Write then Read 179 | 180 | :param write_count: The number of bytes to write 181 | :type write_count: int. 182 | :param read_count: The number of bytes to read 183 | :type read_count: int. 184 | :param write_data: The data bytes to write 185 | :type write_data: bytes. 186 | 187 | :returns: returns data read from SPI 188 | :rtype: bytes 189 | """ 190 | return super()._write_then_read(0x08, write_count, read_count, write_data) 191 | 192 | def extend_aux(self, command: int = I2CExtendAux.LOW) -> bool: 193 | """ 194 | I2C Extend Aux 195 | 196 | :param command: The Aux Config Command to send 197 | :type command: int 198 | 199 | :returns: returns Success or Failure 200 | :rtype: bool. 201 | """ 202 | send_buffer: bytes = [0x09, command] 203 | self.write(send_buffer) 204 | return self.read(1) == 0x01 205 | 206 | 207 | if __name__ == '__main__': 208 | pass 209 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../../src')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = u'pyBusPirate' 23 | copyright = u'2018, Sean Nelson ' 24 | author = u'Sean Nelson ' 25 | 26 | # The short X.Y version 27 | version = u'' 28 | # The full version, including alpha/beta/rc tags 29 | release = u'' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix(es) of source filenames. 49 | # You can specify multiple suffix as a list of string: 50 | # 51 | # source_suffix = ['.rst', '.md'] 52 | source_suffix = '.rst' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | # 60 | # This is also used if you do content translation via gettext catalogs. 61 | # Usually you set "language" from the command line for these cases. 62 | language = None 63 | 64 | # List of patterns, relative to source directory, that match files and 65 | # directories to ignore when looking for source files. 66 | # This pattern also affects html_static_path and html_extra_path . 67 | exclude_patterns = [] 68 | 69 | # The name of the Pygments (syntax highlighting) style to use. 70 | pygments_style = 'sphinx' 71 | 72 | 73 | # -- Options for HTML output ------------------------------------------------- 74 | 75 | # The theme to use for HTML and HTML Help pages. See the documentation for 76 | # a list of builtin themes. 77 | # 78 | html_theme = 'alabaster' 79 | 80 | # Theme options are theme-specific and customize the look and feel of a theme 81 | # further. For a list of options available for each theme, see the 82 | # documentation. 83 | # 84 | # html_theme_options = {} 85 | 86 | # Add any paths that contain custom static files (such as style sheets) here, 87 | # relative to this directory. They are copied after the builtin static files, 88 | # so a file named "default.css" will overwrite the builtin "default.css". 89 | html_static_path = ['_static'] 90 | 91 | # Custom sidebar templates, must be a dictionary that maps document names 92 | # to template names. 93 | # 94 | # The default sidebars (for documents that don't match any pattern) are 95 | # defined by theme itself. Builtin themes are using these templates by 96 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 97 | # 'searchbox.html']``. 98 | # 99 | # html_sidebars = {} 100 | 101 | 102 | # -- Options for HTMLHelp output --------------------------------------------- 103 | 104 | # Output file base name for HTML help builder. 105 | htmlhelp_basename = 'pyBusPiratedoc' 106 | 107 | 108 | # -- Options for LaTeX output ------------------------------------------------ 109 | 110 | latex_elements = { 111 | # The paper size ('letterpaper' or 'a4paper'). 112 | # 113 | # 'papersize': 'letterpaper', 114 | 115 | # The font size ('10pt', '11pt' or '12pt'). 116 | # 117 | # 'pointsize': '10pt', 118 | 119 | # Additional stuff for the LaTeX preamble. 120 | # 121 | # 'preamble': '', 122 | 123 | # Latex figure (float) alignment 124 | # 125 | # 'figure_align': 'htbp', 126 | } 127 | 128 | # Grouping the document tree into LaTeX files. List of tuples 129 | # (source start file, target name, title, 130 | # author, documentclass [howto, manual, or own class]). 131 | latex_documents = [ 132 | (master_doc, 'pyBusPirate.tex', u'pyBusPirate Documentation', 133 | u'Sean Nelson \\textless{}audiohacked@gmail.com\\textgreater{}', 'manual'), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [ 142 | (master_doc, 'pybuspirate', u'pyBusPirate Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'pyBusPirate', u'pyBusPirate Documentation', 154 | author, 'pyBusPirate', 'One line description of project.', 155 | 'Miscellaneous'), 156 | ] 157 | 158 | 159 | # -- Options for Epub output ------------------------------------------------- 160 | 161 | # Bibliographic Dublin Core info. 162 | epub_title = project 163 | epub_author = author 164 | epub_publisher = author 165 | epub_copyright = copyright 166 | 167 | # The unique identifier of the text. This can be a ISBN number 168 | # or the project homepage. 169 | # 170 | # epub_identifier = '' 171 | 172 | # A unique identification for the text. 173 | # 174 | # epub_uid = '' 175 | 176 | # A list of files that should not be packed into the epub file. 177 | epub_exclude_files = ['search.html'] 178 | 179 | 180 | # -- Extension configuration ------------------------------------------------- 181 | -------------------------------------------------------------------------------- /src/buspirate/uart.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ UART class """ 20 | 21 | from enum import IntEnum 22 | from buspirate.base import BusPirate 23 | 24 | 25 | class UartSpeed(IntEnum): 26 | """ UART Speed Enum """ 27 | BAUD_300 = 0b0000 28 | BAUD_1200 = 0b0001 29 | BAUD_2400 = 0b0010 30 | BAUD_4800 = 0b0011 31 | BAUD_9600 = 0b0100 32 | BAUD_19200 = 0b0101 33 | BAUD_31250 = 0b0110 34 | BAUD_MIDI = 0b0110 35 | MIDI = 0b0110 36 | BAUD_38400 = 0b0111 37 | BAUD_57600 = 0b1000 38 | BAUD_115200 = 0b1010 39 | 40 | 41 | class UartConfiguration(object): 42 | """ UART Configuration Enum Base """ 43 | class PinOutput(IntEnum): 44 | """ Enum for Pin Output """ 45 | HIZ = 0b00000 46 | V3P3 = 0b10000 47 | PIN_HIZ = 0b00000 48 | PIN_3P3V = 0b10000 49 | 50 | class DataBitsAndParity(IntEnum): 51 | """ Enum for Data bits and Parity """ 52 | EIGHT_NONE = 0b0000 53 | EIGHT_EVEN = 0b0100 54 | EIGHT_ODD = 0b1000 55 | NINE_NONE = 0b1100 56 | 57 | class StopBits(IntEnum): 58 | """ Enum for Stop bits """ 59 | ONE = 0b00 60 | TWO = 0b10 61 | 62 | class RxPolarity(IntEnum): 63 | """ Enum for Rx Polarity """ 64 | IDLE_1 = 0b0 65 | IDLE_0 = 0b1 66 | 67 | 68 | class UART(BusPirate): 69 | """ UART BitBanging on the BusPirate """ 70 | @property 71 | def enter(self) -> bool: 72 | """ 73 | Enter UART Mode on the BusPirate 74 | 75 | :returns: returns Success or Failure 76 | """ 77 | self.write(0x03) 78 | return self.read(4) == "ART1" 79 | 80 | def echo_rx(self, start_stop: int = 0) -> bool: 81 | """ 82 | Enable disable RX Echoing 83 | 84 | :param start_stop: Give 0 for Start Echo, Give 1 to Stop Echo 85 | :type start_stop: int 86 | 87 | :returns: Success or Failure 88 | :rtype: bool 89 | """ 90 | self.write(0x02 | start_stop) 91 | return self.read(1) == 0x01 92 | 93 | def manual_baudrate(self, brg_register: int = 0x0000) -> bool: 94 | """ 95 | Set Baudrate Manually 96 | 97 | :param brg_register: BRG Register value based on 32mhz osc, divider = 2, and BRGH = 1 98 | :type brg_register: int 99 | 100 | :returns: Success or Failure 101 | :rtype: bool 102 | """ 103 | data = [0x07, brg_register] 104 | self.write(data) 105 | return self.read(3) == [0x01, 0x01, 0x01] 106 | 107 | @property 108 | def bridge_mode(self) -> bool: 109 | """ 110 | Enable Bridge mode. Hard Reset BP to exit. 111 | 112 | :returns: Success or Failure 113 | :rtype: bool 114 | """ 115 | self.write(0x0F) 116 | return self.read(1) == 0x01 117 | 118 | @property 119 | def speed(self): 120 | """ Speed Property Getter """ 121 | return self._speed 122 | 123 | @speed.setter 124 | def speed(self, value): 125 | """ Speed Property Setter """ 126 | self._speed = value 127 | return self.uart_speed(value) 128 | 129 | def uart_speed(self, baudrate: int = UartSpeed.BAUD_115200) -> bool: 130 | """ 131 | Set UART Speed 132 | 133 | :param baudrate: Uart Baud Rates 134 | :type baudrate: int 135 | 136 | :returns: Success or Failure 137 | :rtype: bool 138 | """ 139 | self.write(0x60 | baudrate) 140 | return self.read(1) == 0x01 141 | 142 | @property 143 | def config(self): 144 | """ Configuration Property Getter """ 145 | return self._config 146 | 147 | @config.setter 148 | def config(self, value): 149 | """ Configuration Property Setter """ 150 | self._config = value 151 | pin_outputs = value & 0b1000 152 | data_parity = value & 0b0100 153 | uastop_bits = value & 0b0010 154 | rx_polarity = value & 0b0001 155 | return self.uart_configuration(pin_outputs, data_parity, uastop_bits, rx_polarity) 156 | 157 | def uart_configuration(self, 158 | pin_output: int = UartConfiguration.PinOutput.HIZ, 159 | databits_parity: int = UartConfiguration.DataBitsAndParity.EIGHT_NONE, 160 | stop_bits: int = UartConfiguration.StopBits.ONE, 161 | rx_polarity: int = UartConfiguration.RxPolarity.IDLE_1) -> bool: 162 | """ 163 | UART Configuration 164 | 165 | :param pin_output: The Pin Configuration for Power Pins 166 | :type pin_output: int. 167 | 168 | :param clock_phase: The Pin Configuration for Pull Up Pins 169 | :type clock_phase: int. 170 | 171 | :param clock_edge: The Pin Configuration for AUX pin 172 | :type clock_edge: int. 173 | 174 | :param sample_time: The Pin Configuration for Chip Select Pin 175 | :type sample_time: int. 176 | 177 | :returns: returns Success or Failure 178 | :rtype: bool. 179 | """ 180 | uart_configuration = 0 181 | uart_configuration += pin_output 182 | uart_configuration += databits_parity 183 | uart_configuration += stop_bits 184 | uart_configuration += rx_polarity 185 | self.write(0x80 | uart_configuration) 186 | return self.read(1) == 0x01 187 | 188 | 189 | if __name__ == '__main__': 190 | pass 191 | -------------------------------------------------------------------------------- /tests/test_buspirate_rawwire.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ 20 | Unit Tests for BusPirate SPI class 21 | """ 22 | import unittest 23 | from unittest import mock 24 | 25 | from buspirate import rawwire 26 | 27 | 28 | # pylint: disable=C0111,E1101 29 | class BusPirateRawWireTest(unittest.TestCase): 30 | @mock.patch('serial.Serial', autospec=True) 31 | def setUp(self, mock_serial): # pylint: disable=W0613,W0221 32 | self.bus_pirate = rawwire.RawWire("/dev/ttyUSB0") 33 | 34 | def tearDown(self): 35 | pass 36 | 37 | def test_exit(self): 38 | self.bus_pirate.serial.read.return_value = "BBIO1" 39 | self.assertEqual(self.bus_pirate.exit, True) 40 | self.bus_pirate.serial.write.assert_called_with(0x00) 41 | 42 | def test_mode(self): 43 | self.bus_pirate.serial.read.return_value = "RAW1" 44 | self.assertEqual(self.bus_pirate.mode, "RAW1") 45 | self.bus_pirate.serial.write.assert_called_with(0x01) 46 | 47 | def test_enter(self): 48 | self.bus_pirate.serial.read.return_value = "RAW1" 49 | self.assertEqual(self.bus_pirate.enter, True) 50 | self.bus_pirate.serial.write.assert_called_with(0x05) 51 | 52 | def test_start_bit(self): 53 | self.bus_pirate.serial.read.return_value = 0x01 54 | self.assertEqual(self.bus_pirate.start_bit, True) 55 | self.bus_pirate.serial.write.assert_called_with(0x02) 56 | 57 | def test_stop_bit(self): 58 | self.bus_pirate.serial.read.return_value = 0x01 59 | self.assertEqual(self.bus_pirate.stop_bit, True) 60 | self.bus_pirate.serial.write.assert_called_with(0x03) 61 | 62 | def test_cs_low(self): 63 | self.bus_pirate.serial.read.return_value = 0x01 64 | self.assertEqual(self.bus_pirate.cs_low, True) 65 | self.bus_pirate.serial.write.assert_called_with(0x04) 66 | 67 | def test_cs_high(self): 68 | self.bus_pirate.serial.read.return_value = 0x01 69 | self.assertEqual(self.bus_pirate.cs_high, True) 70 | self.bus_pirate.serial.write.assert_called_with(0x05) 71 | 72 | def test_read_byte(self): 73 | self.bus_pirate.serial.read.side_effect = [0x01, 0xFF] 74 | self.assertEqual(self.bus_pirate.read_byte(), True) 75 | self.bus_pirate.serial.write.assert_called_with(0x06) 76 | 77 | def test_read_bit(self): 78 | self.bus_pirate.serial.read.side_effect = [0x01, 0x01] 79 | self.assertEqual(self.bus_pirate.read_bit(), True) 80 | self.bus_pirate.serial.write.assert_called_with(0x07) 81 | 82 | def test_peek(self): 83 | self.bus_pirate.serial.read.side_effect = [0x01, 0x01] 84 | self.assertEqual(self.bus_pirate.peek(), True) 85 | self.bus_pirate.serial.write.assert_called_with(0x08) 86 | 87 | def test_clock_tick(self): 88 | self.bus_pirate.serial.read.return_value = 0x01 89 | self.assertEqual(self.bus_pirate.clock_tick(), True) 90 | self.bus_pirate.serial.write.assert_called_with(0x09) 91 | 92 | def test_clock_low(self): 93 | self.bus_pirate.serial.read.return_value = 0x01 94 | self.assertEqual(self.bus_pirate.clock_low, True) 95 | self.bus_pirate.serial.write.assert_called_with(0x0A|0x00) 96 | 97 | def test_clock_high(self): 98 | self.bus_pirate.serial.read.return_value = 0x01 99 | self.assertEqual(self.bus_pirate.clock_high, True) 100 | self.bus_pirate.serial.write.assert_called_with(0x0A|0x01) 101 | 102 | def test_data_low(self): 103 | self.bus_pirate.serial.read.return_value = 0x01 104 | self.assertEqual(self.bus_pirate.data_low, True) 105 | self.bus_pirate.serial.write.assert_called_with(0x0C|0x00) 106 | 107 | def test_data_high(self): 108 | self.bus_pirate.serial.read.return_value = 0x01 109 | self.assertEqual(self.bus_pirate.data_high, True) 110 | self.bus_pirate.serial.write.assert_called_with(0x0C|0x01) 111 | 112 | def test_rawwire_bulk_write(self): 113 | read_data = [0x00 for idx in range(1, 17)] 114 | write_data = [idx for idx in range(1, 17)] 115 | self.bus_pirate.serial.read.side_effect = [0x01, read_data] 116 | result = self.bus_pirate.bulk_write(16, write_data) 117 | self.assertEqual(result, read_data) 118 | self.bus_pirate.serial.write.assert_any_call(0x10|0x0F) 119 | self.bus_pirate.serial.write.assert_any_call(write_data) 120 | 121 | def test_bulk_clock_ticks(self): 122 | self.bus_pirate.serial.read.return_value = 0x01 123 | self.assertEqual(self.bus_pirate.bulk_clock_ticks(16), True) 124 | self.bus_pirate.serial.write.assert_called_with(0x20|0x0F) 125 | 126 | def test_bulk_clock_ticks_zero(self): 127 | self.bus_pirate.serial.read.return_value = 0x01 128 | with self.assertRaises(ValueError): 129 | self.bus_pirate.bulk_clock_ticks(0) 130 | 131 | def test_bulk_bits(self): 132 | self.bus_pirate.serial.read.return_value = 0x01 133 | result = self.bus_pirate.bulk_bits(8, 0xFF) 134 | self.assertEqual(result, True) 135 | self.bus_pirate.serial.write.assert_any_call(0x30|0x07) 136 | self.bus_pirate.serial.write.assert_any_call(0xFF) 137 | 138 | def test_bulk_bits_zero_bits(self): 139 | self.bus_pirate.serial.read.return_value = 0x01 140 | with self.assertRaises(ValueError): 141 | self.bus_pirate.bulk_bits(0, 0xFF) 142 | 143 | def test_bulk_bits_nine_bits(self): 144 | self.bus_pirate.serial.read.return_value = 0x01 145 | with self.assertRaises(ValueError): 146 | self.bus_pirate.bulk_bits(9, 0xFF) 147 | 148 | @unittest.skip 149 | def test_bulk_bits_zero_bytes(self): 150 | self.bus_pirate.serial.read.return_value = 0x01 151 | with self.assertRaises(ValueError): 152 | self.bus_pirate.bulk_bits(8, 0x00) 153 | 154 | def test_pullup_voltage_select(self): 155 | with self.assertRaises(NotImplementedError): 156 | self.bus_pirate.pullup_voltage_select() 157 | 158 | def test_rawwire_speed(self): 159 | self.bus_pirate.speed = rawwire.RawWireSpeed.SPEED_100KHZ 160 | self.bus_pirate.serial.read.return_value = 0x01 161 | self.assertEqual(self.bus_pirate.speed, rawwire.RawWireSpeed.SPEED_100KHZ) 162 | self.bus_pirate.serial.write.assert_called_with(0x60|0x02) 163 | 164 | def test_rawwire_config(self): 165 | self.bus_pirate.config = 0b0000 166 | self.bus_pirate.serial.read.return_value = 0x01 167 | self.assertEqual(self.bus_pirate.config, 0b0000) 168 | self.bus_pirate.serial.write.assert_called_with(0x80|0x00) 169 | 170 | @unittest.skip 171 | def test_pic_write(self): 172 | self.bus_pirate.serial.read.return_value = 0x01 173 | self.assertEqual(self.bus_pirate.pic_write(), True) 174 | self.bus_pirate.serial.write.assert_called_with(0x0C|0x01) 175 | 176 | @unittest.skip 177 | def test_pic_read(self): 178 | self.bus_pirate.serial.read.return_value = 0x01 179 | self.assertEqual(self.bus_pirate.pic_read(), True) 180 | self.bus_pirate.serial.write.assert_called_with(0x0C|0x01) 181 | -------------------------------------------------------------------------------- /src/buspirate/base.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ BusPirate Base class """ 20 | 21 | from enum import IntEnum 22 | 23 | import serial 24 | 25 | 26 | class PinConfiguration(IntEnum): 27 | """ Enum for Peripherial Configuration """ 28 | DISABLE = 0b0 29 | ENABLE = 0b1 30 | CHIPSELECT = 0b0001 31 | AUX = 0b0010 32 | PULLUPS = 0b0100 33 | POWER = 0b1000 34 | 35 | 36 | class BusPirate(object): 37 | """ Base Class for BitBanging on BusPirate """ 38 | def __init__(self, port: str, 39 | baudrate: int = 115200, 40 | # bytesize: int = serial.EIGHTBITS, 41 | # parity: int = serial.PARITY_NONE, 42 | # stopbits: int = serial.STOPBITS_ONE, 43 | timeout: float = 0.10, 44 | # xonxoff: bool = False, 45 | # rtscts: bool = False, 46 | # dsrdtr: bool = False, 47 | write_timeout: float = 0.10) -> None: 48 | """ 49 | Init function that also executes the enter function 50 | 51 | :param port: The serial port 52 | :type port: str. 53 | :param baudrate: The serial bitrate 54 | :type baudrate: int. 55 | :param bytesize: The serial byte size 56 | :type bytesize: int. 57 | :param parity: The serial parity 58 | :type parity: int. 59 | :param stopbits: The serial stop bits 60 | :type stopbits: int. 61 | :param timeout: The serial read timeout (default is 0.10 seconds) 62 | :type timeout: float. 63 | :param xonxoff: The serial hardware control 64 | :type xonxoff: bool. 65 | :param rtscts: The serial hardware RTS/CTS 66 | :type rtscts: bool. 67 | :param dsrdtr: The serial hardware DSR/DTR 68 | :type dsrdtr: bool. 69 | :param write_timeout: The serial write timeout (default is 0.10 seconds) 70 | :type write_timeout: float. 71 | 72 | :returns: returns nothing 73 | """ 74 | self._speed = None 75 | self._config = None 76 | self._peripherals = None 77 | self._cs = None 78 | 79 | self.pass_to_super = locals() 80 | self.pass_to_super.pop('self') 81 | # self.pass_to_super.pop('serial_class') 82 | # self.pass_to_super.pop('__class__') 83 | # super(BusPirate, self).__init__(**self.pass_to_super) 84 | self.serial = serial.Serial(**self.pass_to_super) 85 | self.serial.open() 86 | if self.enter: 87 | raise ValueError("Couldn't enter BBIO Mode") 88 | 89 | def write(self, data: bytes = None) -> None: 90 | """ 91 | Send Data to BusPirate 92 | 93 | :param data: The data to send over serial 94 | :type data: bytes 95 | 96 | :returns: returns nothing 97 | """ 98 | self.serial.write(data) 99 | 100 | def read(self, count: int = 1) -> bytes: 101 | """ 102 | Receive Data from BusPirate 103 | 104 | :param count: The number of bytes to receive over serial 105 | :type count: int. 106 | 107 | :returns: returns bytes of data 108 | :rtype: bytes 109 | """ 110 | return self.serial.read(count) 111 | 112 | @property 113 | def enter(self) -> bool: 114 | """ 115 | Enter BitBang Mode on the BusPirate 116 | 117 | :returns: returns Success or Failure 118 | :rtype: bool. 119 | """ 120 | for _ in range(20): 121 | self.write(0x00) 122 | return self.read(5) == "BBIO1" 123 | 124 | @property 125 | def mode(self) -> str: 126 | """ 127 | Get Version and Mode 128 | 129 | :returns: returns Success or Failure 130 | :rtype: bool 131 | """ 132 | self.write(0x01) 133 | return self.read(4) 134 | 135 | @property 136 | def reset(self): 137 | """ 138 | Reset BitBang Mode 139 | 140 | :returns: returns nothing 141 | """ 142 | self.write(0x0F) 143 | 144 | def configure_pins(self): 145 | """ 146 | Configure BusPirate Pins 147 | """ 148 | raise NotImplementedError 149 | 150 | def set_pins(self): 151 | """ 152 | Set BusPirate Pins 153 | """ 154 | raise NotImplementedError 155 | 156 | @property 157 | def peripherals(self): 158 | """ Peripherial Pins Property Getter """ 159 | return self._peripherals 160 | 161 | @peripherals.setter 162 | def peripherals(self, value): 163 | """ Peripherial Pins Property Setter """ 164 | self._peripherals = value 165 | power = value & 0b1000 166 | pullups = value & 0b0100 167 | aux = value & 0b0010 168 | chipselect = value & 0b0001 169 | return self.configure_peripherals(power, pullups, aux, chipselect) 170 | 171 | def configure_peripherals(self, 172 | power: int = PinConfiguration.DISABLE, 173 | pull_ups: int = PinConfiguration.DISABLE, 174 | aux: int = PinConfiguration.DISABLE, 175 | chip_select: int = PinConfiguration.DISABLE) -> bool: 176 | """ 177 | SPI Configure Peripherial Pins 178 | 179 | :param power: The Pin Configuration for Power Pins 180 | :type power: int. 181 | 182 | :param pull_ups: The Pin Configuration for Pull Up Pins 183 | :type pull_ups: int. 184 | 185 | :param aux: The Pin Configuration for AUX pin 186 | :type aux: int. 187 | 188 | :param chip_select: The Pin Configuration for Chip Select Pin 189 | :type chip_select: int. 190 | 191 | :returns: returns Success or Failure 192 | :rtype: bool. 193 | """ 194 | data = 0 195 | data += power 196 | data += pull_ups 197 | data += aux 198 | data += chip_select 199 | self.write(0x40 | data) 200 | return self.read(1) == 0x01 201 | 202 | def bulk_write(self, count: int = 16, data: bytes = None) -> bytes: 203 | """ 204 | Send Bulk Data for Write 205 | """ 206 | if count == 0 or count > 16: 207 | raise ValueError 208 | self.write(0x10 | count-1) 209 | if self.read(1) == 0x01: 210 | if data is None: 211 | data = bytes(count) 212 | self.write(data) 213 | return self.read(count) 214 | return bytes() 215 | 216 | def _write_then_read(self, 217 | command: int = 0, 218 | write_count: int = 0, 219 | read_count: int = 0, 220 | write_data: bytes = None) -> bytes: 221 | """ 222 | Write then Read; used by SPI, I2C, etc 223 | 224 | :param command: The command byte for write_then_read 225 | :type command: int. 226 | :param write_count: The number of bytes to write 227 | :type write_count: int. 228 | :param read_count: The number of bytes to read 229 | :type read_count: int. 230 | :param write_data: The data bytes to write 231 | :type write_data: bytes. 232 | 233 | :returns: returns data read from Bus 234 | :rtype: bytes 235 | """ 236 | assert len(write_data) == write_count, "Given data has incorrect length!" 237 | send_buffer: bytes = [command, write_count, read_count, write_data] 238 | self.write(send_buffer) 239 | recv_buffer = self.read(read_count+1) 240 | if recv_buffer[0] == 0x01: 241 | if len(recv_buffer[1:]) is read_count: 242 | return recv_buffer[1:] 243 | return bytes() 244 | 245 | 246 | if __name__ == '__main__': 247 | pass 248 | -------------------------------------------------------------------------------- /src/buspirate/spi.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ SPI class """ 20 | 21 | from enum import IntEnum 22 | 23 | from buspirate.base import BusPirate 24 | 25 | 26 | class CsLevel(IntEnum): 27 | """ Enum for Chip Select Level """ 28 | LOW = 0b0 29 | HIGH = 0b1 30 | 31 | 32 | class CsSniffTrigger(IntEnum): 33 | """ Enum for Chip Select Sniffer Trigger """ 34 | LOW = 0b10 35 | ALL = 0b01 36 | 37 | 38 | class SpiSpeed(IntEnum): 39 | """ Enum for SPI Speeds """ 40 | SPEED_30KHZ = 0b000 41 | SPEED_125KHZ = 0b001 42 | SPEED_250KHZ = 0b010 43 | SPEED_1MHZ = 0b011 44 | SPEED_2MHZ = 0b100 45 | SPEED_2P6MHZ = 0b101 46 | SPEED_4MHZ = 0b110 47 | SPEED_8MHZ = 0b111 48 | 49 | 50 | class SpiConfiguration(object): 51 | """ SPI Configuration Enum Base """ 52 | # def __getattr__(self, key): 53 | # """ 54 | # Return an attribute of the class (fallback) 55 | # 56 | # :returns: returns contents of class 57 | # :rtype: str. 58 | # """ 59 | # pass 60 | # 61 | # def __getattribute__(self, key): 62 | # """ 63 | # Return an attribute of the class 64 | # 65 | # :returns: returns contents of class 66 | # :rtype: str. 67 | # """ 68 | # pass 69 | # 70 | # def __str__(self) -> str: 71 | # """ 72 | # Return string of the class 73 | # 74 | # :returns: returns contents of class 75 | # :rtype: str. 76 | # """ 77 | # return str(self.__dict__) 78 | # 79 | # def __eq__(self, other: object = None) -> bool: 80 | # """ 81 | # Compare SPI Configurations 82 | # 83 | # :returns: returns a boolean 84 | # :rtype: bool. 85 | # """ 86 | # return self == other 87 | 88 | class PinOutput(IntEnum): 89 | """ Enum for Pin Output """ 90 | HIZ = 0b0 91 | PIN_HIZ = 0b0 92 | V3P3 = 0b1 93 | PIN_3P3V = 0b1 94 | 95 | class ClockPhase(IntEnum): 96 | """ Enum for Clock Phase """ 97 | LOW = 0b0 98 | 99 | class ClockEdge(IntEnum): 100 | """ Enum for Clock Edge """ 101 | IDLE_TO_ACTIVE = 0b0 102 | ACTIVE_TO_IDLE = 0b1 103 | 104 | class SampleTime(IntEnum): 105 | """ Enum for Sample Time """ 106 | MIDDLE = 0b0 107 | 108 | 109 | class SPI(BusPirate): 110 | """ SPI BitBanging on the BusPirate """ 111 | @property 112 | def enter(self) -> bool: 113 | """ 114 | Enter BitBang Mode on the BusPirate 115 | 116 | :returns: returns Success or Failure 117 | :rtype: bool. 118 | """ 119 | self.write(0x01) 120 | return self.read(4) == "SPI1" 121 | 122 | @property 123 | def cs(self): 124 | """ Chip Select Property Getter """ 125 | return self._cs 126 | 127 | @cs.setter 128 | def cs(self, value): 129 | """ Chip Select Property Setter """ 130 | self._cs = value 131 | self.chip_select(value) 132 | 133 | def chip_select(self, level: int = CsLevel.LOW) -> bool: 134 | """ 135 | SPI Chip Select 136 | 137 | :param level: The Active Level for SPI Chip Select 138 | :type level: int. 139 | 140 | :returns: returns Success or Failure 141 | :rtype: bool. 142 | """ 143 | self.write(0x02 | level) 144 | return self.read(1) == 0x01 145 | 146 | def sniff(self, trigger: int = CsSniffTrigger.LOW) -> bool: 147 | """ 148 | SPI Sniffer 149 | 150 | :param trigger: The trigger type for SPI Sniffer 151 | :type trigger: int. 152 | 153 | :returns: returns Success or Failure 154 | :rtype: bool. 155 | """ 156 | self.write(0x0C | trigger) 157 | return self.read(1) == 0x01 158 | 159 | @property 160 | def speed(self): 161 | """ Speed Property Getter """ 162 | return self._speed 163 | 164 | @speed.setter 165 | def speed(self, value): 166 | """ Speed Property Setter """ 167 | self._speed = value 168 | return self.spi_speed(value) 169 | 170 | def spi_speed(self, spi_speed: int = SpiSpeed.SPEED_30KHZ) -> bool: 171 | """ 172 | SPI Speed Configuration 173 | 174 | :param spi_speed: The SPI Clock Rate 175 | :type spi_speed: int. 176 | 177 | :returns: returns Success or Failure 178 | :rtype: bool 179 | """ 180 | self.write(0x60 | spi_speed) 181 | return self.read(1) == 0x01 182 | 183 | @property 184 | def config(self): 185 | """ Pin Config Property Getter """ 186 | return self._config 187 | 188 | @config.setter 189 | def config(self, value): 190 | """ Ping Config Property Setter """ 191 | self._config = value 192 | pin_outputs = value & 0b1000 193 | clock_phase = value & 0b0100 194 | clock_edges = value & 0b0010 195 | sampletimes = value & 0b0001 196 | return self.spi_configuration(pin_outputs, clock_phase, clock_edges, sampletimes) 197 | 198 | def spi_configuration(self, 199 | pin_output: int = SpiConfiguration.PinOutput.HIZ, 200 | clock_phase: int = SpiConfiguration.ClockPhase.LOW, 201 | clock_edge: int = SpiConfiguration.ClockEdge.IDLE_TO_ACTIVE, 202 | sample_time: int = SpiConfiguration.SampleTime.MIDDLE) -> bool: 203 | """ 204 | SPI Configuration 205 | 206 | :param pin_output: The Pin Configuration for Power Pins 207 | :type pin_output: int. 208 | 209 | :param clock_phase: The Pin Configuration for Pull Up Pins 210 | :type clock_phase: int. 211 | 212 | :param clock_edge: The Pin Configuration for AUX pin 213 | :type clock_edge: int. 214 | 215 | :param sample_time: The Pin Configuration for Chip Select Pin 216 | :type sample_time: int. 217 | 218 | :returns: returns Success or Failure 219 | :rtype: bool. 220 | """ 221 | spi_configuration = 0 222 | spi_configuration += pin_output << 3 223 | spi_configuration += clock_phase << 2 224 | spi_configuration += clock_edge << 1 225 | spi_configuration += sample_time 226 | self.write(0x80 | spi_configuration) 227 | return self.read(1) == 0x01 228 | 229 | def write_then_read(self, 230 | write_count: int = 0, 231 | read_count: int = 0, 232 | write_data: bytes = None) -> bytes: 233 | """ 234 | SPI Write then Read 235 | 236 | :param write_count: The number of bytes to write 237 | :type write_count: int. 238 | :param read_count: The number of bytes to read 239 | :type read_count: int. 240 | :param write_data: The data bytes to write 241 | :type write_data: bytes. 242 | 243 | :returns: returns data read from SPI 244 | :rtype: bytes 245 | """ 246 | return super()._write_then_read(0x04, write_count, read_count, write_data) 247 | 248 | def write_then_read_with_no_cs(self, 249 | write_count: int = 0, 250 | read_count: int = 0, 251 | write_data: bytes = None) -> bool: 252 | """ 253 | SPI Write then Read with No Chip Select transistion 254 | 255 | :param write_count: The number of bytes to write 256 | :type write_count: int. 257 | :param read_count: The number of bytes to read 258 | :type read_count: int. 259 | :param write_data: The data bytes to write 260 | :type write_data: bytes. 261 | 262 | :returns: returns data read from SPI 263 | :rtype: bytes 264 | """ 265 | return super()._write_then_read(0x05, write_count, read_count, write_data) 266 | 267 | 268 | if __name__ == '__main__': 269 | pass 270 | -------------------------------------------------------------------------------- /src/buspirate/rawwire.py: -------------------------------------------------------------------------------- 1 | # Created by Sean Nelson on 2018-08-19. 2 | # Copyright 2018 Sean Nelson 3 | # 4 | # This file is part of pyBusPirate. 5 | # 6 | # pyBusPirate is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # pyBusPirate is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with pyBusPirate. If not, see . 18 | 19 | """ RawWire class """ 20 | 21 | from enum import IntEnum 22 | 23 | from buspirate.base import BusPirate 24 | 25 | 26 | class RawWireSpeed(IntEnum): 27 | """ Enum for RawWire Speeds """ 28 | SPEED_5KHZ = 0b000 29 | SPEED_50KHZ = 0b001 30 | SPEED_100KHZ = 0b010 31 | SPEED_400KHZ = 0b011 32 | 33 | 34 | class RawWireConfiguration(object): 35 | """ RawWire Configuration Enum Base """ 36 | # def __getattr__(self, key): 37 | # """ 38 | # Return an attribute of the class (fallback) 39 | # 40 | # :returns: returns contents of class 41 | # :rtype: str. 42 | # """ 43 | # pass 44 | # 45 | # def __getattribute__(self, key): 46 | # """ 47 | # Return an attribute of the class 48 | # 49 | # :returns: returns contents of class 50 | # :rtype: str. 51 | # """ 52 | # pass 53 | # 54 | # def __str__(self) -> str: 55 | # """ 56 | # Return string of the class 57 | # 58 | # :returns: returns contents of class 59 | # :rtype: str. 60 | # """ 61 | # return str(self.__dict__) 62 | # 63 | # def __eq__(self, other: object = None) -> bool: 64 | # """ 65 | # Compare SPI Configurations 66 | # 67 | # :returns: returns a boolean 68 | # :rtype: bool. 69 | # """ 70 | # return self == other 71 | 72 | class PinOutput(IntEnum): 73 | """ Enum for Pin Output """ 74 | HIZ = 0b0000 75 | V3P3 = 0b1000 76 | PIN_HIZ = 0b0000 77 | PIN_3P3V = 0b1000 78 | 79 | class WireProtocol(IntEnum): 80 | """ Enum for Wire Protocol """ 81 | PROTOCOL_2WIRE = 0b0000 82 | PROTOCOL_3WIRE = 0b0100 83 | 84 | class BitOrder(IntEnum): 85 | """ Enum for Bit Order """ 86 | MSB = 0b0000 87 | LSB = 0b0010 88 | 89 | class NotUsed(IntEnum): 90 | """ Enum for Position Z in RawWireConfiguration """ 91 | pass 92 | 93 | 94 | class RawWire(BusPirate): 95 | """ RawWire BitBanging on the BusPirate """ 96 | @property 97 | def exit(self): 98 | """ 99 | Exit RawWire Mode 100 | 101 | :returns: returns Success or Failure 102 | """ 103 | self.write(0x00) 104 | return self.read(5) == "BBIO1" 105 | 106 | @property 107 | def enter(self): 108 | """ 109 | Enter RawWire Mode 110 | 111 | :returns: returns Success or Failure 112 | """ 113 | self.write(0x05) 114 | return self.read(4) == "RAW1" 115 | 116 | @property 117 | def start_bit(self): 118 | """ 119 | Start Bit 120 | 121 | :returns: returns Success or Failure 122 | """ 123 | self.write(0x02) 124 | return self.read(1) == 0x01 125 | 126 | @property 127 | def stop_bit(self): 128 | """ 129 | Stop Bit 130 | 131 | :returns: returns Success or Failure 132 | """ 133 | self.write(0x03) 134 | return self.read(1) == 0x01 135 | 136 | @property 137 | def cs_low(self): 138 | """ 139 | Toggle Chip Select Low 140 | 141 | :returns: returns Success or Failure 142 | """ 143 | self.write(0x04) 144 | return self.read(1) == 0x01 145 | 146 | @property 147 | def cs_high(self): 148 | """ 149 | Toggle Chip Select High 150 | 151 | :returns: returns Success or Failure 152 | """ 153 | self.write(0x05) 154 | return self.read(1) == 0x01 155 | 156 | def read_byte(self): 157 | """ 158 | Read Byte from Bus 159 | 160 | :returns: returns Success or Failure 161 | """ 162 | self.write(0x06) 163 | return self.read(1) 164 | 165 | def read_bit(self): 166 | """ 167 | Read Bit From Bus 168 | 169 | :returns: returns Success or Failure 170 | """ 171 | self.write(0x07) 172 | return self.read(1) 173 | 174 | def peek(self): 175 | """ 176 | Peek at Bus without toggling CS or something 177 | 178 | :returns: returns Success or Failure 179 | """ 180 | self.write(0x08) 181 | return self.read(1) 182 | 183 | def clock_tick(self): 184 | """ 185 | Jiggle Clock 186 | 187 | :returns: returns Success or Failure 188 | """ 189 | self.write(0x09) 190 | return self.read(1) == 0x01 191 | 192 | @property 193 | def clock_low(self): 194 | """ 195 | Toggle Clock Low 196 | 197 | :returns: returns Success or Failure 198 | """ 199 | self.write(0x0A) 200 | return self.read(1) == 0x01 201 | 202 | @property 203 | def clock_high(self): 204 | """ 205 | Toggle Clock High 206 | 207 | :returns: returns Success or Failure 208 | """ 209 | self.write(0x0B) 210 | return self.read(1) == 0x01 211 | 212 | @property 213 | def data_low(self): 214 | """ 215 | Toggle Data line Low 216 | 217 | :returns: returns Success or Failure 218 | """ 219 | self.write(0x0C) 220 | return self.read(1) == 0x01 221 | 222 | @property 223 | def data_high(self): 224 | """ 225 | Toggle Data line High 226 | 227 | :returns: returns Success or Failure 228 | """ 229 | self.write(0x0D) 230 | return self.read(1) == 0x01 231 | 232 | def bulk_clock_ticks(self, count: int = 16): 233 | """ 234 | Send Bulk Clock ticks 235 | 236 | :returns: returns Success or Failure 237 | """ 238 | if count == 0 or count > 16: 239 | raise ValueError 240 | self.write(0x20 | count-1) 241 | return self.read(1) == 0x01 242 | 243 | def bulk_bits(self, count: int = 8, data_byte: int = 0x00): 244 | """ 245 | Send Bulk bits of a byte 246 | 247 | :returns: returns Success or Failure 248 | """ 249 | if count == 0 or count > 8: 250 | raise ValueError 251 | self.write(0x30 | count-1) 252 | self.write(data_byte) 253 | return self.read(1) == 0x01 254 | 255 | def pullup_voltage_select(self) -> None: 256 | """ 257 | Select Pull-Up Voltage 258 | 259 | Unimplmented! 260 | """ 261 | raise NotImplementedError 262 | 263 | @property 264 | def speed(self): 265 | """ Speed Property Getter """ 266 | return self._speed 267 | 268 | @speed.setter 269 | def speed(self, value): 270 | """ Speed Property Setter """ 271 | self._speed = value 272 | self.rawwire_speed(value) 273 | 274 | def rawwire_speed(self, rawwire_speed: int = RawWireSpeed.SPEED_400KHZ) -> bool: 275 | """ 276 | Raw Wire Speed Configuration 277 | 278 | :param rawwire_speed: The Clock Rate 279 | :type rawwire_speed: int. 280 | 281 | :returns: returns Success or Failure 282 | :rtype: bool 283 | """ 284 | self.write(0x60 | rawwire_speed) 285 | return self.read(1) == 0x01 286 | 287 | @property 288 | def config(self): 289 | """ Configuration Property Getter """ 290 | return self._config 291 | 292 | @config.setter 293 | def config(self, value): 294 | """ Configuration Property Setter """ 295 | self._config = value 296 | p_output = value & 0b1000 297 | protocol = value & 0b0100 298 | bitorder = value & 0b0010 299 | return self.rawwire_config(p_output, protocol, bitorder) 300 | 301 | def rawwire_config(self, 302 | pin_output: int = RawWireConfiguration.PinOutput.HIZ, 303 | wire_protocol: int = RawWireConfiguration.WireProtocol.PROTOCOL_2WIRE, 304 | bit_order: int = RawWireConfiguration.BitOrder.MSB) -> bool: 305 | """ 306 | Raw Wire Configuration 307 | 308 | :param pin_output: The Pin Configuration for Pin Output 309 | :type pin_output: int. 310 | 311 | :param wire_protocol: The Raw Wire Configuration for Protocol 312 | :type wire_protocol: int. 313 | 314 | :param bit_order: The Raw Wire Configuration for First Bit Order 315 | :type bit_order: int. 316 | 317 | :returns: returns Success or Failure 318 | :rtype: bool. 319 | """ 320 | rawwire_configuration = 0 321 | rawwire_configuration += pin_output 322 | rawwire_configuration += wire_protocol 323 | rawwire_configuration += bit_order 324 | 325 | self.write(0x80 | rawwire_configuration) 326 | return self.read(1) == 0x01 327 | 328 | 329 | if __name__ == '__main__': 330 | pass 331 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "astroid" 11 | version = "2.6.2" 12 | description = "An abstract syntax tree for Python with inference support." 13 | category = "dev" 14 | optional = false 15 | python-versions = "~=3.6" 16 | 17 | [package.dependencies] 18 | lazy-object-proxy = ">=1.4.0" 19 | typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} 20 | typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} 21 | wrapt = ">=1.11,<1.13" 22 | 23 | [[package]] 24 | name = "atomicwrites" 25 | version = "1.4.0" 26 | description = "Atomic file writes." 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 30 | 31 | [[package]] 32 | name = "attrs" 33 | version = "21.2.0" 34 | description = "Classes Without Boilerplate" 35 | category = "dev" 36 | optional = false 37 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 38 | 39 | [package.extras] 40 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 41 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 42 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 43 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 44 | 45 | [[package]] 46 | name = "colorama" 47 | version = "0.4.4" 48 | description = "Cross-platform colored terminal text." 49 | category = "dev" 50 | optional = false 51 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 52 | 53 | [[package]] 54 | name = "coverage" 55 | version = "5.5" 56 | description = "Code coverage measurement for Python" 57 | category = "dev" 58 | optional = false 59 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 60 | 61 | [package.extras] 62 | toml = ["toml"] 63 | 64 | [[package]] 65 | name = "distlib" 66 | version = "0.3.2" 67 | description = "Distribution utilities" 68 | category = "dev" 69 | optional = false 70 | python-versions = "*" 71 | 72 | [[package]] 73 | name = "filelock" 74 | version = "3.0.12" 75 | description = "A platform independent file lock." 76 | category = "dev" 77 | optional = false 78 | python-versions = "*" 79 | 80 | [[package]] 81 | name = "importlib-metadata" 82 | version = "4.6.1" 83 | description = "Read metadata from Python packages" 84 | category = "dev" 85 | optional = false 86 | python-versions = ">=3.6" 87 | 88 | [package.dependencies] 89 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 90 | zipp = ">=0.5" 91 | 92 | [package.extras] 93 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 94 | perf = ["ipython"] 95 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 96 | 97 | [[package]] 98 | name = "importlib-resources" 99 | version = "5.2.0" 100 | description = "Read resources from Python packages" 101 | category = "dev" 102 | optional = false 103 | python-versions = ">=3.6" 104 | 105 | [package.dependencies] 106 | zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} 107 | 108 | [package.extras] 109 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 110 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] 111 | 112 | [[package]] 113 | name = "iniconfig" 114 | version = "1.1.1" 115 | description = "iniconfig: brain-dead simple config-ini parsing" 116 | category = "dev" 117 | optional = false 118 | python-versions = "*" 119 | 120 | [[package]] 121 | name = "isort" 122 | version = "5.9.2" 123 | description = "A Python utility / library to sort Python imports." 124 | category = "dev" 125 | optional = false 126 | python-versions = ">=3.6.1,<4.0" 127 | 128 | [package.extras] 129 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 130 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 131 | colors = ["colorama (>=0.4.3,<0.5.0)"] 132 | plugins = ["setuptools"] 133 | 134 | [[package]] 135 | name = "lazy-object-proxy" 136 | version = "1.6.0" 137 | description = "A fast and thorough lazy object proxy." 138 | category = "dev" 139 | optional = false 140 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 141 | 142 | [[package]] 143 | name = "mccabe" 144 | version = "0.6.1" 145 | description = "McCabe checker, plugin for flake8" 146 | category = "dev" 147 | optional = false 148 | python-versions = "*" 149 | 150 | [[package]] 151 | name = "packaging" 152 | version = "21.0" 153 | description = "Core utilities for Python packages" 154 | category = "dev" 155 | optional = false 156 | python-versions = ">=3.6" 157 | 158 | [package.dependencies] 159 | pyparsing = ">=2.0.2" 160 | 161 | [[package]] 162 | name = "pluggy" 163 | version = "0.13.1" 164 | description = "plugin and hook calling mechanisms for python" 165 | category = "dev" 166 | optional = false 167 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 168 | 169 | [package.dependencies] 170 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 171 | 172 | [package.extras] 173 | dev = ["pre-commit", "tox"] 174 | 175 | [[package]] 176 | name = "py" 177 | version = "1.10.0" 178 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 179 | category = "dev" 180 | optional = false 181 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 182 | 183 | [[package]] 184 | name = "pylint" 185 | version = "2.9.3" 186 | description = "python code static checker" 187 | category = "dev" 188 | optional = false 189 | python-versions = "~=3.6" 190 | 191 | [package.dependencies] 192 | astroid = ">=2.6.2,<2.7" 193 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 194 | isort = ">=4.2.5,<6" 195 | mccabe = ">=0.6,<0.7" 196 | toml = ">=0.7.1" 197 | 198 | [[package]] 199 | name = "pyparsing" 200 | version = "2.4.7" 201 | description = "Python parsing module" 202 | category = "dev" 203 | optional = false 204 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 205 | 206 | [[package]] 207 | name = "pyserial" 208 | version = "3.5" 209 | description = "Python Serial Port Extension" 210 | category = "main" 211 | optional = false 212 | python-versions = "*" 213 | 214 | [package.extras] 215 | cp2110 = ["hidapi"] 216 | 217 | [[package]] 218 | name = "pytest" 219 | version = "6.2.4" 220 | description = "pytest: simple powerful testing with Python" 221 | category = "dev" 222 | optional = false 223 | python-versions = ">=3.6" 224 | 225 | [package.dependencies] 226 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 227 | attrs = ">=19.2.0" 228 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 229 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 230 | iniconfig = "*" 231 | packaging = "*" 232 | pluggy = ">=0.12,<1.0.0a1" 233 | py = ">=1.8.2" 234 | toml = "*" 235 | 236 | [package.extras] 237 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 238 | 239 | [[package]] 240 | name = "pytest-cov" 241 | version = "2.12.1" 242 | description = "Pytest plugin for measuring coverage." 243 | category = "dev" 244 | optional = false 245 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 246 | 247 | [package.dependencies] 248 | coverage = ">=5.2.1" 249 | pytest = ">=4.6" 250 | toml = "*" 251 | 252 | [package.extras] 253 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] 254 | 255 | [[package]] 256 | name = "six" 257 | version = "1.16.0" 258 | description = "Python 2 and 3 compatibility utilities" 259 | category = "dev" 260 | optional = false 261 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 262 | 263 | [[package]] 264 | name = "toml" 265 | version = "0.10.2" 266 | description = "Python Library for Tom's Obvious, Minimal Language" 267 | category = "dev" 268 | optional = false 269 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 270 | 271 | [[package]] 272 | name = "tox" 273 | version = "3.23.1" 274 | description = "tox is a generic virtualenv management and test command line tool" 275 | category = "dev" 276 | optional = false 277 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 278 | 279 | [package.dependencies] 280 | colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} 281 | filelock = ">=3.0.0" 282 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 283 | packaging = ">=14" 284 | pluggy = ">=0.12.0" 285 | py = ">=1.4.17" 286 | six = ">=1.14.0" 287 | toml = ">=0.9.4" 288 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 289 | 290 | [package.extras] 291 | docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] 292 | testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] 293 | 294 | [[package]] 295 | name = "typed-ast" 296 | version = "1.4.3" 297 | description = "a fork of Python 2 and 3 ast modules with type comment support" 298 | category = "dev" 299 | optional = false 300 | python-versions = "*" 301 | 302 | [[package]] 303 | name = "typing-extensions" 304 | version = "3.10.0.0" 305 | description = "Backported and Experimental Type Hints for Python 3.5+" 306 | category = "dev" 307 | optional = false 308 | python-versions = "*" 309 | 310 | [[package]] 311 | name = "virtualenv" 312 | version = "20.4.7" 313 | description = "Virtual Python Environment builder" 314 | category = "dev" 315 | optional = false 316 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 317 | 318 | [package.dependencies] 319 | appdirs = ">=1.4.3,<2" 320 | distlib = ">=0.3.1,<1" 321 | filelock = ">=3.0.0,<4" 322 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 323 | importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} 324 | six = ">=1.9.0,<2" 325 | 326 | [package.extras] 327 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] 328 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] 329 | 330 | [[package]] 331 | name = "wrapt" 332 | version = "1.12.1" 333 | description = "Module for decorators, wrappers and monkey patching." 334 | category = "dev" 335 | optional = false 336 | python-versions = "*" 337 | 338 | [[package]] 339 | name = "zipp" 340 | version = "3.5.0" 341 | description = "Backport of pathlib-compatible object wrapper for zip files" 342 | category = "dev" 343 | optional = false 344 | python-versions = ">=3.6" 345 | 346 | [package.extras] 347 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 348 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 349 | 350 | [metadata] 351 | lock-version = "1.1" 352 | python-versions = "^3.6.2" 353 | content-hash = "463ab530145921e28880f80f5f4157703d5c5befc175e53023127a11e29cb686" 354 | 355 | [metadata.files] 356 | appdirs = [ 357 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 358 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 359 | ] 360 | astroid = [ 361 | {file = "astroid-2.6.2-py3-none-any.whl", hash = "sha256:606b2911d10c3dcf35e58d2ee5c97360e8477d7b9f3efc3f24811c93e6fc2cd9"}, 362 | {file = "astroid-2.6.2.tar.gz", hash = "sha256:38b95085e9d92e2ca06cf8b35c12a74fa81da395a6f9e65803742e6509c05892"}, 363 | ] 364 | atomicwrites = [ 365 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 366 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 367 | ] 368 | attrs = [ 369 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 370 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 371 | ] 372 | colorama = [ 373 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 374 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 375 | ] 376 | coverage = [ 377 | {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, 378 | {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, 379 | {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, 380 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, 381 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, 382 | {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, 383 | {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, 384 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, 385 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, 386 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, 387 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, 388 | {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, 389 | {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, 390 | {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, 391 | {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, 392 | {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, 393 | {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, 394 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, 395 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, 396 | {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, 397 | {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, 398 | {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, 399 | {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, 400 | {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, 401 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, 402 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, 403 | {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, 404 | {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, 405 | {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, 406 | {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, 407 | {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, 408 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, 409 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, 410 | {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, 411 | {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, 412 | {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, 413 | {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, 414 | {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, 415 | {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, 416 | {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, 417 | {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, 418 | {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, 419 | {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, 420 | {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, 421 | {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, 422 | {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, 423 | {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, 424 | {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, 425 | {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, 426 | {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, 427 | {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, 428 | {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, 429 | ] 430 | distlib = [ 431 | {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, 432 | {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, 433 | ] 434 | filelock = [ 435 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 436 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 437 | ] 438 | importlib-metadata = [ 439 | {file = "importlib_metadata-4.6.1-py3-none-any.whl", hash = "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"}, 440 | {file = "importlib_metadata-4.6.1.tar.gz", hash = "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac"}, 441 | ] 442 | importlib-resources = [ 443 | {file = "importlib_resources-5.2.0-py3-none-any.whl", hash = "sha256:a0143290bef3cbc99de9e40176e4987780939a955b8632f02ce6c935f42e9bfc"}, 444 | {file = "importlib_resources-5.2.0.tar.gz", hash = "sha256:22a2c42d8c6a1d30aa8a0e1f57293725bfd5c013d562585e46aff469e0ff78b3"}, 445 | ] 446 | iniconfig = [ 447 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 448 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 449 | ] 450 | isort = [ 451 | {file = "isort-5.9.2-py3-none-any.whl", hash = "sha256:eed17b53c3e7912425579853d078a0832820f023191561fcee9d7cae424e0813"}, 452 | {file = "isort-5.9.2.tar.gz", hash = "sha256:f65ce5bd4cbc6abdfbe29afc2f0245538ab358c14590912df638033f157d555e"}, 453 | ] 454 | lazy-object-proxy = [ 455 | {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, 456 | {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, 457 | {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, 458 | {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, 459 | {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, 460 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, 461 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, 462 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, 463 | {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, 464 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, 465 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, 466 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, 467 | {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, 468 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, 469 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, 470 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, 471 | {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, 472 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, 473 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, 474 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, 475 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, 476 | {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, 477 | ] 478 | mccabe = [ 479 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 480 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 481 | ] 482 | packaging = [ 483 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 484 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 485 | ] 486 | pluggy = [ 487 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 488 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 489 | ] 490 | py = [ 491 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 492 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 493 | ] 494 | pylint = [ 495 | {file = "pylint-2.9.3-py3-none-any.whl", hash = "sha256:5d46330e6b8886c31b5e3aba5ff48c10f4aa5e76cbf9002c6544306221e63fbc"}, 496 | {file = "pylint-2.9.3.tar.gz", hash = "sha256:23a1dc8b30459d78e9ff25942c61bb936108ccbe29dd9e71c01dc8274961709a"}, 497 | ] 498 | pyparsing = [ 499 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 500 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 501 | ] 502 | pyserial = [ 503 | {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, 504 | {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, 505 | ] 506 | pytest = [ 507 | {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, 508 | {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, 509 | ] 510 | pytest-cov = [ 511 | {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, 512 | {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, 513 | ] 514 | six = [ 515 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 516 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 517 | ] 518 | toml = [ 519 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 520 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 521 | ] 522 | tox = [ 523 | {file = "tox-3.23.1-py2.py3-none-any.whl", hash = "sha256:b0b5818049a1c1997599d42012a637a33f24c62ab8187223fdd318fa8522637b"}, 524 | {file = "tox-3.23.1.tar.gz", hash = "sha256:307a81ddb82bd463971a273f33e9533a24ed22185f27db8ce3386bff27d324e3"}, 525 | ] 526 | typed-ast = [ 527 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 528 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 529 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 530 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 531 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 532 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 533 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 534 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 535 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 536 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 537 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 538 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 539 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 540 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 541 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 542 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 543 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 544 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 545 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 546 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 547 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 548 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 549 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 550 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 551 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 552 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 553 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 554 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 555 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 556 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 557 | ] 558 | typing-extensions = [ 559 | {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, 560 | {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, 561 | {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, 562 | ] 563 | virtualenv = [ 564 | {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, 565 | {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, 566 | ] 567 | wrapt = [ 568 | {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, 569 | ] 570 | zipp = [ 571 | {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, 572 | {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, 573 | ] 574 | --------------------------------------------------------------------------------