├── .gitignore ├── .readthedocs.yml ├── LICENSE ├── README.md ├── docs ├── Makefile └── source │ ├── conf.py │ ├── index.rst │ └── pyHydrabus.rst ├── pyHydrabus ├── __init__.py ├── auxpin.py ├── common.py ├── hydrabus.py ├── i2c.py ├── mmc.py ├── nfc.py ├── onewire.py ├── protocol.py ├── rawwire.py ├── sdio.py ├── smartcard.py ├── spi.py ├── swd.py ├── uart.py └── utils.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | pyHydrabus.egg-info/* 3 | build/* 4 | dist/* 5 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/source/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF and ePub 17 | formats: all 18 | 19 | # Optionally set the version of Python and requirements required to build your docs 20 | python: 21 | version: 3.6 22 | install: 23 | - requirements: requirements.txt 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Nicolas OBERLI 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyHydrabus 2 | 3 | A python (3.6+) module that can be used to control the [HydraFW](https://github.com/hydrabus/hydrafw) from a Python script. 4 | 5 | ## Installing 6 | 7 | ### From source 8 | 9 | Clone this repository, then run the following command : 10 | 11 | ``` 12 | $ python setup.py install --user 13 | ``` 14 | 15 | ### From Pypi 16 | 17 | pyHydrabus is also available on [Pypi](https://pypi.org/project/pyHydrabus/) 18 | 19 | ``` 20 | $ pip install pyHydrabus 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### ReadTheDocs 26 | 27 | The documentation is available [online](https://pyhydrabus.readthedocs.io/en/latest/). 28 | 29 | ### Sphinx 30 | 31 | The library is self-documented using docstrings. 32 | The API documentation is also available using [Sphinx](http://www.sphinx-doc.org). 33 | 34 | 35 | Get into the `docs/` folder, then build the documentation using the following command : 36 | 37 | ``` 38 | $ make html 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /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('../../')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'pyHydrabus' 23 | copyright = '2024, Baldanos' 24 | author = 'Baldanos' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '' 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 | 'sphinx.ext.doctest', 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # 52 | # source_suffix = ['.rst', '.md'] 53 | source_suffix = '.rst' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | # 61 | # This is also used if you do content translation via gettext catalogs. 62 | # Usually you set "language" from the command line for these cases. 63 | language = "en" 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | # This pattern also affects html_static_path and html_extra_path. 68 | exclude_patterns = [] 69 | 70 | # The name of the Pygments (syntax highlighting) style to use. 71 | pygments_style = None 72 | 73 | 74 | # -- Options for HTML output ------------------------------------------------- 75 | 76 | # The theme to use for HTML and HTML Help pages. See the documentation for 77 | # a list of builtin themes. 78 | # 79 | html_theme = 'alabaster' 80 | 81 | # Theme options are theme-specific and customize the look and feel of a theme 82 | # further. For a list of options available for each theme, see the 83 | # documentation. 84 | # 85 | # html_theme_options = {} 86 | 87 | # Add any paths that contain custom static files (such as style sheets) here, 88 | # relative to this directory. They are copied after the builtin static files, 89 | # so a file named "default.css" will overwrite the builtin "default.css". 90 | #html_static_path = ['_static'] 91 | 92 | # Custom sidebar templates, must be a dictionary that maps document names 93 | # to template names. 94 | # 95 | # The default sidebars (for documents that don't match any pattern) are 96 | # defined by theme itself. Builtin themes are using these templates by 97 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 98 | # 'searchbox.html']``. 99 | # 100 | # html_sidebars = {} 101 | 102 | 103 | # -- Options for HTMLHelp output --------------------------------------------- 104 | 105 | # Output file base name for HTML help builder. 106 | htmlhelp_basename = 'pyHydrabusdoc' 107 | 108 | 109 | # -- Options for LaTeX output ------------------------------------------------ 110 | 111 | latex_elements = { 112 | # The paper size ('letterpaper' or 'a4paper'). 113 | # 114 | # 'papersize': 'letterpaper', 115 | 116 | # The font size ('10pt', '11pt' or '12pt'). 117 | # 118 | # 'pointsize': '10pt', 119 | 120 | # Additional stuff for the LaTeX preamble. 121 | # 122 | # 'preamble': '', 123 | 124 | # Latex figure (float) alignment 125 | # 126 | # 'figure_align': 'htbp', 127 | } 128 | 129 | # Grouping the document tree into LaTeX files. List of tuples 130 | # (source start file, target name, title, 131 | # author, documentclass [howto, manual, or own class]). 132 | latex_documents = [ 133 | (master_doc, 'pyHydrabus.tex', 'pyHydrabus Documentation', 134 | 'Baldanos', 'manual'), 135 | ] 136 | 137 | 138 | # -- Options for manual page output ------------------------------------------ 139 | 140 | # One entry per manual page. List of tuples 141 | # (source start file, name, description, authors, manual section). 142 | man_pages = [ 143 | (master_doc, 'pyhydrabus', 'pyHydrabus Documentation', 144 | [author], 1) 145 | ] 146 | 147 | 148 | # -- Options for Texinfo output ---------------------------------------------- 149 | 150 | # Grouping the document tree into Texinfo files. List of tuples 151 | # (source start file, target name, title, author, 152 | # dir menu entry, description, category) 153 | texinfo_documents = [ 154 | (master_doc, 'pyHydrabus', 'pyHydrabus Documentation', 155 | author, 'pyHydrabus', 'One line description of project.', 156 | 'Miscellaneous'), 157 | ] 158 | 159 | 160 | # -- Options for Epub output ------------------------------------------------- 161 | 162 | # Bibliographic Dublin Core info. 163 | epub_title = project 164 | 165 | # The unique identifier of the text. This can be a ISBN number 166 | # or the project homepage. 167 | # 168 | # epub_identifier = '' 169 | 170 | # A unique identification for the text. 171 | # 172 | # epub_uid = '' 173 | 174 | # A list of files that should not be packed into the epub file. 175 | epub_exclude_files = ['search.html'] 176 | 177 | 178 | # -- Extension configuration ------------------------------------------------- 179 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pyHydrabus documentation master file, created by 2 | sphinx-quickstart on Mon Jan 7 22:39:47 2019. 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 pyHydrabus's documentation! 7 | ====================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 4 11 | 12 | pyHydrabus.rst 13 | -------------------------------------------------------------------------------- /docs/source/pyHydrabus.rst: -------------------------------------------------------------------------------- 1 | pyHydrabus package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyHydrabus.hydrabus module 8 | -------------------------- 9 | 10 | .. automodule:: pyHydrabus.hydrabus 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyHydrabus.i2c module 16 | --------------------- 17 | 18 | .. automodule:: pyHydrabus.i2c 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyHydrabus.onewire module 24 | ------------------------- 25 | 26 | .. automodule:: pyHydrabus.onewire 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyHydrabus.protocol module 32 | -------------------------- 33 | 34 | .. automodule:: pyHydrabus.protocol 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyHydrabus.rawwire module 40 | ------------------------- 41 | 42 | .. automodule:: pyHydrabus.rawwire 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | pyHydrabus.smartcard module 48 | --------------------------- 49 | 50 | .. automodule:: pyHydrabus.smartcard 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | pyHydrabus.spi module 56 | --------------------- 57 | 58 | .. automodule:: pyHydrabus.spi 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | pyHydrabus.Utils module 64 | ----------------------- 65 | 66 | .. automodule:: pyHydrabus.Utils 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | pyHydrabus.uart module 72 | ---------------------- 73 | 74 | .. automodule:: pyHydrabus.uart 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | pyHydrabus.swd module 80 | ---------------------- 81 | 82 | .. automodule:: pyHydrabus.swd 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | 87 | pyHydrabus.aux module 88 | ---------------------- 89 | 90 | .. automodule:: pyHydrabus.auxpin 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | 95 | pyHydrabus.nfc module 96 | ---------------------- 97 | 98 | .. automodule:: pyHydrabus.nfc 99 | :members: 100 | :undoc-members: 101 | :show-inheritance: 102 | 103 | pyHydrabus.mmc module 104 | ---------------------- 105 | 106 | .. automodule:: pyHydrabus.mmc 107 | :members: 108 | :undoc-members: 109 | :show-inheritance: 110 | 111 | pyHydrabus.sdio module 112 | ---------------------- 113 | 114 | .. automodule:: pyHydrabus.sdio 115 | :members: 116 | :undoc-members: 117 | :show-inheritance: 118 | 119 | 120 | Module contents 121 | --------------- 122 | 123 | .. automodule:: pyHydrabus 124 | :members: 125 | :undoc-members: 126 | :show-inheritance: 127 | -------------------------------------------------------------------------------- /pyHydrabus/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from .spi import * 16 | from .i2c import * 17 | from .onewire import * 18 | from .rawwire import * 19 | from .smartcard import * 20 | from .uart import * 21 | from .utils import * 22 | from .swd import * 23 | from .nfc import * 24 | from .mmc import * 25 | from .sdio import * 26 | 27 | import logging 28 | 29 | logger = logging.getLogger(__name__) 30 | logger.addHandler(logging.NullHandler()) 31 | 32 | INPUT = 1 33 | OUTPUT = 0 34 | 35 | name = "pyHydrabus" 36 | __version__ = "0.2.11" 37 | -------------------------------------------------------------------------------- /pyHydrabus/auxpin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from .common import set_bit 4 | 5 | 6 | class AUXPin: 7 | """ 8 | Auxilary pin base class 9 | 10 | This class is meant to be used in any mode and is instanciated in the 11 | Protocol class 12 | 13 | :example: 14 | 15 | >>> import pyHydrabus 16 | >>> i=pyHydrabus.RawWire('/dev/hydrabus') 17 | >>> # Set AUX pin 0 (PC4) to output 18 | >>> i.AUX[0].direction = pyHydrabus.OUTPUT 19 | >>> # Set AUX pin to logical 1 20 | >>> i.AUX[0].value = 1 21 | >>> # Read Auxiliary pin 2 (PC5) value 22 | >>> i.AUX[1].value 23 | 24 | """ 25 | 26 | OUTPUT = 0 27 | INPUT = 1 28 | 29 | def __init__(self, number, hydrabus): 30 | """ 31 | AUXPin constructor 32 | 33 | :param number: Auxilary pin number (0-3) 34 | :param hydrabus: Initialized pyHydrabus.Hydrabus instance 35 | """ 36 | self.number = number 37 | self._hydrabus = hydrabus 38 | self._logger = logging.getLogger(__name__) 39 | 40 | def _get_config(self): 41 | """ 42 | Gets Auxiliary pin config from Hydrabus 43 | 44 | :return: Auxilary pin config (4 bits pullup for AUX[0-3], 4 bits value for AUX[0-3]) 45 | :rtype: byte 46 | """ 47 | CMD = 0b11100000 48 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 49 | return self._hydrabus.read(1) 50 | 51 | def _get_values(self): 52 | """ 53 | Gets Auxiliary pin values from Hydrabus 54 | 55 | :return: Auxilary pin values AUX[0-3] 56 | :rtype: byte 57 | """ 58 | CMD = 0b11000000 59 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 60 | return self._hydrabus.read(1) 61 | 62 | @property 63 | def value(self): 64 | """ 65 | Auxiliary pin getter 66 | """ 67 | return (ord(self._get_values()) >> self.number) & 0b1 68 | 69 | @value.setter 70 | def value(self, value): 71 | """ 72 | Auxiliary pin setter 73 | 74 | :param value: auxiliary pin value (0 or 1) 75 | """ 76 | CMD = 0b11010000 77 | CMD = CMD | ord(set_bit(self._get_values(), value, self.number)) 78 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 79 | if self._hydrabus.read(1) == b"\x01": 80 | return 81 | else: 82 | self._logger.error("Error setting auxiliary pins value.") 83 | 84 | def toggle(self): 85 | """ 86 | Toggle pin state 87 | """ 88 | self.value = not self.value 89 | 90 | @property 91 | def direction(self): 92 | """ 93 | Auxiliary pin direction getter 94 | 95 | :return: The pin direction (0=output, 1=input) 96 | :rtype: int 97 | """ 98 | return (ord(self._get_config()) >> self.number) & 0b1 99 | 100 | @direction.setter 101 | def direction(self, value): 102 | """ 103 | Auxiliary pin direction setter 104 | 105 | :param value: The pin direction (0=output, 1=input) 106 | """ 107 | CMD = 0b11110000 108 | PARAM = self._get_config() 109 | PARAM = set_bit(PARAM, value, self.number) 110 | 111 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 112 | self._hydrabus.write(PARAM) 113 | if self._hydrabus.read(1) == b"\x01": 114 | return 115 | else: 116 | self._logger.error("Error setting auxiliary pins direction.") 117 | 118 | @property 119 | def pullup(self): 120 | """ 121 | Auxiliary pin pullup getter 122 | 123 | :return: Auxiliary pin pullup status (1=enabled, 0=disabled") 124 | :rtype: int 125 | """ 126 | return (ord(self._get_config()) >> (4 + self.number)) & 0b1 127 | 128 | @pullup.setter 129 | def pullup(self, value): 130 | """ 131 | Auxiliary pin pullup setter 132 | 133 | :param value: Auxiliary pin pullup (1=enabled, 0=disabled") 134 | """ 135 | CMD = 0b11110000 136 | PARAM = self._get_config() 137 | PARAM = set_bit(PARAM, value, 4 + self.number) 138 | 139 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 140 | self._hydrabus.write(PARAM) 141 | if self._hydrabus.read(1) == b"\x01": 142 | return 143 | else: 144 | self._logger.error("Error setting auxiliary pins value.") 145 | -------------------------------------------------------------------------------- /pyHydrabus/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | def split(seq, length): 17 | """ 18 | Split a list in chunks of specific length 19 | """ 20 | return [seq[i : i + length] for i in range(0, len(seq), length)] 21 | 22 | 23 | def set_bit(byte, bit, position): 24 | v = ord(byte) 25 | if bit == 1: 26 | v = v | (1 << position) 27 | elif bit == 0: 28 | v = v & (~(1 << position)) & 0xFF 29 | else: 30 | raise ValueError("Bit must be 0 or 1") 31 | return v.to_bytes(1, byteorder="big") 32 | -------------------------------------------------------------------------------- /pyHydrabus/hydrabus.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import time 16 | import logging 17 | 18 | import serial 19 | import serial.tools.list_ports 20 | 21 | 22 | class Hydrabus: 23 | """ 24 | Base class for all modes. 25 | 26 | Manages all serial communication 27 | """ 28 | 29 | def __init__(self, port=""): 30 | """ 31 | Class init 32 | 33 | :param port: Serial port to use. Will automatically be probed if absent 34 | """ 35 | self._logger = logging.getLogger(__name__) 36 | 37 | 38 | if port == "": 39 | for port in serial.tools.list_ports.comports(): 40 | if "HydraBus" in port.description: 41 | self.port = port.device 42 | self._logger.info(f"Autodetected Hydrabus on {self.port}") 43 | else: 44 | self.port = port 45 | 46 | try: 47 | self._serialport = serial.Serial(self.port, timeout=None) 48 | self.enter_bbio() 49 | except serial.SerialException as e: 50 | self._logger.error(f"Cannot connect : {e.strerror}") 51 | raise type(e)(f"Cannot open {self.port}") 52 | 53 | def write(self, data=b""): 54 | """ 55 | Base write primitive 56 | 57 | :param data: Bytes to write 58 | :type data: bytes 59 | """ 60 | if not self.connected: 61 | raise serial.SerialException("Not connected.") 62 | try: 63 | self._logger.debug(f"==>[{str(len(data)).zfill(4)}] {data.hex()}") 64 | self._serialport.write(data) 65 | self._serialport.flush() 66 | except serial.SerialException as e: 67 | self._logger.error(f"Cannot send : {e.strerror}") 68 | raise type(e)(f"Cannot send : {e.strerror}") 69 | 70 | def read(self, length=1): 71 | """ 72 | Base read primitive 73 | 74 | :param length: Number of bytes to read 75 | :type length: int 76 | :return: Read data 77 | :rtype: bytes 78 | """ 79 | if not self.connected: 80 | raise serial.SerialException("Not connected.") 81 | try: 82 | data = self._serialport.read(length) 83 | self._logger.debug(f"<==[{str(length).zfill(4)}] {data.hex()}") 84 | self._lastread = data 85 | return data 86 | except serial.SerialException as e: 87 | self._logger.error(f"Cannot read : {e.strerror}") 88 | raise type(e)(f"Cannot read : {e.strerror}") 89 | 90 | def flush_input(self): 91 | """ 92 | Flush input buffer (data from Hydrabus) 93 | """ 94 | self._serialport.reset_input_buffer() 95 | 96 | @property 97 | def in_waiting(self): 98 | """ 99 | Return the number of bytes in the receive buffer. 100 | 101 | :return: Number of bytes 102 | :rtype: int 103 | """ 104 | return self._serialport.in_waiting 105 | 106 | def exit_bbio(self): 107 | """ 108 | Reset Hydrabus to CLI mode 109 | 110 | :return: Bool 111 | """ 112 | if not self.connected: 113 | raise serial.SerialException("Not connected.") 114 | if self.reset() == True: 115 | self.write(b"\x00") 116 | self.write(b"\x0F\n") 117 | return True 118 | else: 119 | return False 120 | 121 | def enter_bbio(self): 122 | """ 123 | Enter BBIO mode. 124 | 125 | This should be done prior all further operations 126 | 127 | :return: Bool 128 | """ 129 | if not self.connected: 130 | raise serial.SerialException("Not connected.") 131 | self.timeout = 0.01 132 | for _ in range(20): 133 | self.write(b"\x00") 134 | if b"BBIO1" in self.read(5): 135 | self.flush_input() 136 | self.timeout = None 137 | return True 138 | self._logger.error("Cannot enter BBIO mode.") 139 | self.timeout = None 140 | return False 141 | 142 | def reset(self): 143 | """ 144 | Force reset to BBIO main mode 145 | 146 | :return: Bool 147 | """ 148 | timeout = time.time() + 10 149 | if not self.connected: 150 | raise serial.SerialException("Not connected.") 151 | self.timeout = 0.1 152 | while self.read(5) != b"BBIO1": 153 | self.flush_input() 154 | self.write(b"\x00") 155 | if time.time() > timeout: 156 | self._logger.error(f"Unable to reset hydrabus") 157 | return False 158 | self.timeout = None 159 | return True 160 | 161 | def identify(self): 162 | """ 163 | Identify the current mode 164 | 165 | :return: Current mode 166 | :rtype: str 167 | """ 168 | if not self.connected: 169 | raise serial.SerialException("Not connected.") 170 | self.write(b"\x01") 171 | return self.read(4).decode("ascii") 172 | 173 | def close(self): 174 | """ 175 | Close the serial port 176 | """ 177 | self._serialport.close() 178 | 179 | @property 180 | def timeout(self): 181 | """ 182 | Serial port read timeout 183 | 184 | :return: timeout 185 | :rtype: int 186 | """ 187 | return self._serialport.timeout 188 | 189 | @timeout.setter 190 | def timeout(self, value): 191 | """ 192 | Set serial port read timeout 193 | 194 | :param value: timeout 195 | :type value: int 196 | """ 197 | self._serialport.timeout = value 198 | 199 | @property 200 | def connected(self): 201 | """ 202 | Check if serial port is opened 203 | 204 | :return: Bool 205 | """ 206 | return self._serialport.is_open 207 | -------------------------------------------------------------------------------- /pyHydrabus/i2c.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | 18 | 19 | class I2C(Protocol): 20 | """ 21 | I2C protocol handler 22 | 23 | :example: 24 | 25 | >>> #Read data from an EEPROM 26 | >>> import pyHydrabus 27 | >>> i=pyHydrabus.I2C('/dev/hydrabus') 28 | >>> i.set_speed(pyHydrabus.I2C.I2C_SPEED_100K) 29 | >>> i.pullup=1 30 | >>> i.start();i.bulk_write(b'\xa0\x00');print(i.write_read(b'\xa1',64)) 31 | 32 | """ 33 | 34 | __I2C_DEFAULT_CONFIG = 0b000 35 | 36 | I2C_SPEED_50K = 0b00 37 | I2C_SPEED_100K = 0b01 38 | I2C_SPEED_400K = 0b10 39 | I2C_SPEED_1M = 0b11 40 | 41 | def __init__(self, port=""): 42 | self._config = self.__I2C_DEFAULT_CONFIG 43 | super().__init__(name=b"I2C1", fname="I2C", mode_byte=b"\x02", port=port) 44 | self._configure_port() 45 | 46 | def start(self): 47 | """ 48 | Send a I2C start condition 49 | """ 50 | self._hydrabus.write(b"\x02") 51 | if self._hydrabus.read(1) == b"\x00": 52 | self._logger.error("Cannot execute command.") 53 | return False 54 | return True 55 | 56 | def stop(self): 57 | """ 58 | Send a I2C stop condition 59 | """ 60 | self._hydrabus.write(b"\x03") 61 | if self._hydrabus.read(1) == b"\x00": 62 | self._logger.error("Cannot execute command.") 63 | return False 64 | return True 65 | 66 | def read_byte(self): 67 | """ 68 | Read a byte from the I2C bus 69 | """ 70 | self._hydrabus.write(b"\x04") 71 | return self._hydrabus.read(1) 72 | 73 | def send_ack(self): 74 | """ 75 | Send an ACK 76 | Used with the read_byte() function 77 | """ 78 | self._hydrabus.write(b"\x06") 79 | if self._hydrabus.read(1) == b"\x00": 80 | self._logger.error("Cannot execute command.") 81 | return False 82 | return True 83 | 84 | def send_nack(self): 85 | """ 86 | Send a NACK 87 | Used with the read_byte() function 88 | """ 89 | self._hydrabus.write(b"\x07") 90 | if self._hydrabus.read(1) == b"\x00": 91 | self._logger.error("Cannot execute command.") 92 | return False 93 | return True 94 | 95 | def write_read(self, data=b"", read_len=0): 96 | """ 97 | Write-then-read operation 98 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-I2C-mode-guide#i2c-write-then-read-0b00001000 99 | 100 | This method sends a start condition before writing and a stop condition after reading. 101 | 102 | :param data: Data to be sent 103 | :type data: bytes 104 | :param read_len: Number of bytes to read 105 | :type read_len: int 106 | :return: Read data 107 | :rtype: bytes 108 | """ 109 | CMD = 0b00001000 110 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 111 | 112 | self._hydrabus.write(len(data).to_bytes(2, byteorder="big")) 113 | 114 | self._hydrabus.write(read_len.to_bytes(2, byteorder="big")) 115 | 116 | self._hydrabus.timeout = 0 117 | if self._hydrabus.read(1) == b"\x00": 118 | self._logger.error("Cannot execute command. Too many bytes requested ?") 119 | return None 120 | self._hydrabus.timeout = None 121 | 122 | self._hydrabus.write(data) 123 | 124 | if self._hydrabus.read(1) != b"\x01": 125 | self._logger.warn("Data not ACKed. Aborting.") 126 | return None 127 | 128 | return self._hydrabus.read(read_len) 129 | 130 | def write(self, data=b""): 131 | """ 132 | Write on I2C bus 133 | 134 | :param data: data to be sent 135 | :type data: bytes 136 | """ 137 | self.write_read(data, read_len=0) 138 | 139 | def read(self, length=0): 140 | """ 141 | Read on I2C bus 142 | 143 | :param length: Number of bytes to read 144 | :type length: int 145 | :return: Read data 146 | :rtype: bytes 147 | """ 148 | result = [] 149 | for _ in range(length - 1): 150 | result.append(self.read_byte()) 151 | self.send_ack() 152 | result.append(self.read_byte()) 153 | self.send_nack() 154 | 155 | return b"".join(result) 156 | 157 | def bulk_write(self, data=b""): 158 | """ 159 | Bulk write on I2C bus 160 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-I2C-mode-guide#bulk-i2c-write-0b0001xxxx 161 | 162 | :param data: Data to be sent 163 | :type data: bytes 164 | 165 | :return: ACK status of the written bytes (b'\x00'=ACK, b'\x01'=NACK) 166 | :rtype: list 167 | """ 168 | CMD = 0b00010000 169 | if not len(data) > 0: 170 | raise ValueError("Send at least one byte") 171 | if not len(data) <= 16: 172 | raise ValueError("Too many bytes to write") 173 | CMD = CMD | (len(data) - 1) 174 | 175 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 176 | if self._hydrabus.read(1) != b"\x01": 177 | self._logger.warn("Unknown error.") 178 | return None 179 | 180 | self._hydrabus.write(data) 181 | 182 | return self._hydrabus.read(len(data)) 183 | 184 | def scan(self): 185 | """ 186 | Scan I2C bus and returns all available device addresses in a list 187 | 188 | :return: List of available device addresses 189 | :rtype: list 190 | """ 191 | result = [] 192 | for i in range(1, 0x78): 193 | addr = (i << 1).to_bytes(1, byteorder="big") 194 | self.start() 195 | if self.bulk_write(addr) == b"\x00": 196 | addr = int.from_bytes(addr, byteorder="big") >> 1 197 | result.append(addr) 198 | self.stop() 199 | return result 200 | 201 | def set_speed(self, speed): 202 | """ 203 | Set I2C bus speed 204 | 205 | :param speed: Select any of the defined speeds (I2C_SPEED_*) 206 | """ 207 | if not speed <= 0b11: 208 | raise ValueError("Incorrect speed") 209 | CMD = 0b01100000 210 | CMD = CMD | speed 211 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 212 | 213 | if self._hydrabus.read(1) == b"\x01": 214 | return True 215 | else: 216 | self._logger.error("Error setting speed.") 217 | return False 218 | 219 | def clock_stretch(self, clock_stretch_val): 220 | """ 221 | Set I2C clock stretching timeout in number of clocks 222 | 223 | :param clock_stretch_val: clock stretching timeout number of clocks (0 disable up to 2^32) 224 | """ 225 | CMD = 0b00100000 226 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 227 | self._hydrabus.write(clock_stretch_val.to_bytes(4, byteorder="big")) 228 | 229 | if self._hydrabus.read(1) == b"\x01": 230 | return True 231 | else: 232 | self._logger.error("Error setting clock stretch.") 233 | return False 234 | 235 | def _configure_port(self): 236 | CMD = 0b01000000 237 | CMD = CMD | self._config 238 | 239 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 240 | if self._hydrabus.read(1) == b"\x01": 241 | return True 242 | else: 243 | self._logger.error("Error setting config.") 244 | return False 245 | 246 | @property 247 | def pullup(self): 248 | """ 249 | Pullup status (0=off, 1=on) 250 | """ 251 | if self._config & 0b100: 252 | return 1 253 | else: 254 | return 0 255 | 256 | @pullup.setter 257 | def pullup(self, value): 258 | if value == 0: 259 | self._config = self._config & ~(1 << 2) 260 | else: 261 | self._config = self._config | (1 << 2) 262 | self._configure_port() 263 | -------------------------------------------------------------------------------- /pyHydrabus/mmc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | from .common import split 18 | 19 | 20 | class MMC(Protocol): 21 | """ 22 | MMC protocol handler 23 | 24 | :example: 25 | 26 | >>> import pyHydrabus 27 | >>> m=pyHydrabus.MMC('/dev/hydrabus') 28 | >>> # Get CID 29 | >>> m.cid 30 | >>> # Get CSD 31 | >>> m.csd 32 | >>> # Read block 0 33 | >>> m.read(0) 34 | 35 | """ 36 | 37 | __MMC_DEFAULT_CONFIG = 0b0 38 | 39 | def __init__(self, port=""): 40 | self._rf = 0 41 | self._mode = 0 42 | self._config = self.__MMC_DEFAULT_CONFIG 43 | super().__init__(name=b"MMC1", fname="eMMC", mode_byte=b"\x0d", port=port) 44 | 45 | def _configure_port(self): 46 | CMD = 0b10000000 47 | CMD = CMD | self._config 48 | 49 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 50 | if self._hydrabus.read(1) == b"\x01": 51 | return True 52 | else: 53 | self._logger.error("Error setting config.") 54 | return False 55 | 56 | @property 57 | def cid(self): 58 | CMD = 0b00000010 59 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 60 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 61 | return self._hydrabus.read(16) 62 | 63 | @property 64 | def csd(self): 65 | CMD = 0b00000011 66 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 67 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 68 | return self._hydrabus.read(16) 69 | 70 | @property 71 | def ext_csd(self): 72 | CMD = 0b00000110 73 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 74 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 75 | return self._hydrabus.read(512) 76 | 77 | @property 78 | def bus_width(self): 79 | """ 80 | Data bus width (1 or 4) 81 | """ 82 | if self._config & 0b1: 83 | return 4 84 | else: 85 | return 1 86 | 87 | @bus_width.setter 88 | def bus_width(self, value): 89 | if value == 1: 90 | self._config = self._config & ~(1) 91 | elif value == 4: 92 | self._config = self._config | (1) 93 | else: 94 | print("Invalid value (1 or 4)") 95 | self._configure_port() 96 | 97 | 98 | def write(self, data=b"", block_num=0): 99 | """ 100 | Write MMC block 101 | 102 | :param data: Data to be written (512 bytes) 103 | :type data: bytes 104 | :param block_num: Block number 105 | :type block_num: int 106 | 107 | :param data: Data to be written 108 | :type data: bytes 109 | """ 110 | CMD = 0b00000101 111 | 112 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 113 | self._hydrabus.write(block_num.to_bytes(4, byteorder="big")) 114 | self._hydrabus.write(data) 115 | 116 | 117 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 118 | 119 | return status == 1 120 | 121 | def read(self, block_num=0): 122 | """ 123 | Read MMC block 124 | 125 | :param block_num: Block number 126 | :type block_num: int 127 | 128 | :return: Bytes read 129 | :rtype: bytes 130 | """ 131 | CMD = 0b00000100 132 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 133 | self._hydrabus.write(block_num.to_bytes(4, byteorder="big")) 134 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 135 | if status == 1: 136 | return self._hydrabus.read(512) 137 | else: 138 | return b'' 139 | 140 | -------------------------------------------------------------------------------- /pyHydrabus/nfc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | from .common import split 18 | 19 | 20 | class NFC(Protocol): 21 | """ 22 | NFC protocol handler 23 | 24 | :example: 25 | 26 | >>> import pyHydrabus 27 | >>> n=pyHydrabus.NFC('/dev/hydrabus') 28 | >>> # Set mode to ISO 14443A 29 | >>> n.mode = pyHydrabus.NFC.MODE_ISO_14443A 30 | >>> # Set radio ON 31 | >>> n.rf = 1 32 | >>> # Send REQA, get ATQA 33 | >>> n.write_bits(b'\x26', 7) 34 | >>> # Send anticol, read ID 35 | >>> n.write(b'\x93\x20', 0).hex() 36 | 37 | """ 38 | 39 | MODE_ISO_14443A = 0 40 | MODE_ISO_15693 = 1 41 | 42 | def __init__(self, port=""): 43 | self._rf = 0 44 | self._mode = 0 45 | super().__init__(name=b"NFC1", fname="NFC-Reader", mode_byte=b"\x0c", port=port) 46 | 47 | @property 48 | def mode(self): 49 | return self._mode 50 | 51 | @mode.setter 52 | def mode(self, value): 53 | CMD = 0b00000110 54 | CMD |= value 55 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 56 | self._mode = value 57 | 58 | @property 59 | def rf(self): 60 | return self._rf 61 | 62 | @rf.setter 63 | def rf(self, value): 64 | CMD = 0b00000010 65 | CMD |= value 66 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 67 | self._rf = value 68 | 69 | def write(self, data=b"", crc=0): 70 | """ 71 | Write bytes on NFC 72 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-NFC-Reader-mode-guide#send-bytes-0b00000101 73 | 74 | :param data: Data to be sent 75 | :type data: bytes 76 | """ 77 | CMD = 0b00000101 78 | 79 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 80 | self._hydrabus.write(crc.to_bytes(1, byteorder="big")) 81 | self._hydrabus.write(len(data).to_bytes(1, byteorder="big")) 82 | self._hydrabus.write(data) 83 | 84 | rx_len = int.from_bytes(self._hydrabus.read(1), byteorder="little") 85 | 86 | return self._hydrabus.read(rx_len) 87 | 88 | def write_bits(self, data=b"", num_bits=0): 89 | """ 90 | Write bits on NFC 91 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-NFC-Reader-mode-guide#send-bits-0b00000100 92 | 93 | :param data: Data to be sent 94 | :type data: byte 95 | :param num_bits: number of bits to send 96 | :type num_bits: int 97 | """ 98 | CMD = 0b00000100 99 | 100 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 101 | self._hydrabus.write(data) 102 | self._hydrabus.write(num_bits.to_bytes(1, byteorder="big")) 103 | 104 | rx_len = int.from_bytes(self._hydrabus.read(1), byteorder="little") 105 | 106 | return self._hydrabus.read(rx_len) 107 | -------------------------------------------------------------------------------- /pyHydrabus/onewire.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | from .common import split 18 | 19 | 20 | class OneWire(Protocol): 21 | """ 22 | One wire protocol handler 23 | 24 | :example: 25 | 26 | TODO 27 | 28 | """ 29 | 30 | __ONEWIRE_DEFAULT_CONFIG = 0b100 31 | 32 | def __init__(self, port=""): 33 | self._config = self.__ONEWIRE_DEFAULT_CONFIG 34 | super().__init__(name=b"1W01", fname="1-Wire", mode_byte=b"\x04", port=port) 35 | self._configure_port() 36 | 37 | def reset(self): 38 | """ 39 | Send a 1-Wire reset 40 | """ 41 | self._hydrabus.write(b"\x02") 42 | return True 43 | 44 | def read_byte(self): 45 | """ 46 | Read a byte from the 1-Wire bus 47 | """ 48 | self._hydrabus.write(b"\x04") 49 | return self._hydrabus.read(1) 50 | 51 | def write(self, data=b""): 52 | """ 53 | Write on 1-Wire bus 54 | 55 | :param data: data to be sent 56 | :type data: bytes 57 | """ 58 | for chunk in split(data, 16): 59 | self.bulk_write(chunk) 60 | 61 | def read(self, read_len=0): 62 | """ 63 | Read on 1-Wire bus 64 | 65 | :param read_len: Number of bytes to be read 66 | :return read_len: int 67 | :return: Read data 68 | :rtype: bytes 69 | """ 70 | return b"".join([self.read_byte() for x in range(read_len)]) 71 | 72 | def bulk_write(self, data=b""): 73 | """ 74 | Bulk write on 1-Wire bus 75 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-1-Wire-mode-guide#bulk-1-wire-transfer-0b0001xxxx 76 | 77 | :param data: Data to be sent 78 | :type data: bytes 79 | """ 80 | CMD = 0b00010000 81 | if not len(data) > 0: 82 | raise ValueError("Send at least one byte") 83 | if not len(data) <= 16: 84 | raise ValueError("Too many bytes to write") 85 | CMD = CMD | (len(data) - 1) 86 | 87 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 88 | self._hydrabus.write(data) 89 | 90 | if self._hydrabus.read(1) != b"\x01": 91 | self._logger.warn("Unknown error.") 92 | 93 | def _configure_port(self): 94 | CMD = 0b01000000 95 | CMD = CMD | self._config 96 | 97 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 98 | if self._hydrabus.read(1) == b"\x01": 99 | return True 100 | else: 101 | self._logger.error("Error setting config.") 102 | return False 103 | 104 | @property 105 | def pullup(self): 106 | """ 107 | Pullup status (0=off, 1=on) 108 | """ 109 | if self._config & 0b100: 110 | return 1 111 | else: 112 | return 0 113 | 114 | @pullup.setter 115 | def pullup(self, value): 116 | if value == 0: 117 | self._config = self._config & ~(1 << 2) 118 | else: 119 | self._config = self._config | (1 << 2) 120 | self._configure_port() 121 | 122 | def swio_init(self): 123 | self._config = 0b1000 124 | self._configure_port() 125 | 126 | def swio_read_reg(self, address): 127 | """ 128 | Read a debug register 129 | """ 130 | CMD = 0b00100000 131 | self._hydrabus.write(CMD.to_bytes(1, byteorder="little")) 132 | self._hydrabus.write(address.to_bytes(1, byteorder="little")) 133 | 134 | return int.from_bytes(self._hydrabus.read(4), byteorder="little") 135 | 136 | def swio_write_reg(self, address, value): 137 | """ 138 | Write a debug register 139 | """ 140 | CMD = 0b00110000 141 | self._hydrabus.write(CMD.to_bytes(1, byteorder="little")) 142 | self._hydrabus.write(address.to_bytes(1, byteorder="little")) 143 | self._hydrabus.write(value.to_bytes(4, byteorder="little")) 144 | 145 | if self._hydrabus.read(1) == b"\x01": 146 | return True 147 | else: 148 | self._logger.error("Unknown error.") 149 | return False 150 | 151 | -------------------------------------------------------------------------------- /pyHydrabus/protocol.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .hydrabus import Hydrabus 17 | from .auxpin import AUXPin 18 | 19 | 20 | class Protocol: 21 | """ 22 | Base class for all supported protocols 23 | 24 | :param name: Name of the protocol (returned by Hydrabus) eg. SPI1 25 | :type name: str 26 | :param fname: Full name of the protocol 27 | :type fname: str 28 | :param mode_byte: Byte used to enter the mode (eg. \x01 for SPI) 29 | :type mode_byte: bytes 30 | :param port: The port name 31 | :type port: str 32 | """ 33 | 34 | def __init__(self, name="", fname="", mode_byte=b"\x00", port=""): 35 | self.name = name 36 | self.fname = fname 37 | self._mode_byte = mode_byte 38 | self._hydrabus = Hydrabus(port) 39 | self._logger = logging.getLogger(__name__) 40 | 41 | self._enter() 42 | self._hydrabus.flush_input() 43 | 44 | self.AUX = [] 45 | for i in range(4): 46 | self.AUX.append(AUXPin(i, self._hydrabus)) 47 | 48 | def _enter(self): 49 | self._hydrabus.write(self._mode_byte) 50 | if self._hydrabus.read(4) == self.name: 51 | self._hydrabus.mode = self.name 52 | return True 53 | else: 54 | self._logger.error(f"Cannot enter mode.") 55 | return False 56 | 57 | def _exit(self): 58 | return self._hydrabus.reset() 59 | 60 | def identify(self): 61 | """ 62 | Identify the current mode 63 | 64 | :return: The current mode identifier (4 bytes) 65 | :rtype: str 66 | """ 67 | return self._hydrabus.identify() 68 | 69 | def close(self): 70 | """ 71 | Close the communication channel and resets Hydrabus 72 | """ 73 | self._hydrabus.exit_bbio() 74 | self._hydrabus.close() 75 | 76 | @property 77 | def hydrabus(self): 78 | """ 79 | Return _hydrabus instance to access Hydrabus class functions and serial methods 80 | from any protocol classes instances 81 | 82 | :return: _hydrabus class instance 83 | """ 84 | return self._hydrabus 85 | 86 | @property 87 | def timeout(self): 88 | return self._hydrabus.timeout 89 | 90 | @timeout.setter 91 | def timeout(self, value): 92 | self._hydrabus.timeout = value 93 | -------------------------------------------------------------------------------- /pyHydrabus/rawwire.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | from .common import split 18 | 19 | 20 | class RawWire(Protocol): 21 | """ 22 | Raw wire protocol handler 23 | 24 | :example: 25 | 26 | >>> import pyHydrabus 27 | >>> r=pyHydrabus.RawWire('/dev/hydrabus') 28 | >>> # Set SDA to high 29 | >>> r.sda = 1 30 | >>> # Send two clock ticks 31 | >>> r.clocks(2) 32 | >>> # Read two bytes 33 | >>> data = r.read(2) 34 | 35 | """ 36 | 37 | __RAWWIRE_DEFAULT_CONFIG = 0b000 38 | 39 | def __init__(self, port=""): 40 | self._config = self.__RAWWIRE_DEFAULT_CONFIG 41 | self._clk = 0 42 | self._sda = 0 43 | super().__init__(name=b"RAW1", fname="Raw-Wire", mode_byte=b"\x05", port=port) 44 | self._configure_port() 45 | 46 | def read_bit(self): 47 | """ 48 | Sends a clock tick, and return the read bit value 49 | """ 50 | CMD = 0b00000111 51 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 52 | return self._hydrabus.read(1) 53 | 54 | def read_byte(self): 55 | """ 56 | Read a byte from the raw wire 57 | 58 | :return: The read byte 59 | :rtype: bytes 60 | """ 61 | CMD = 0b00000110 62 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 63 | return self._hydrabus.read(1) 64 | 65 | def clock(self): 66 | """ 67 | Send a clock tick 68 | """ 69 | CMD = 0b00001001 70 | 71 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 72 | if self._hydrabus.read(1) == b"\x01": 73 | return True 74 | else: 75 | self._logger.error("Error setting pin.") 76 | return False 77 | 78 | def bulk_ticks(self, num): 79 | """ 80 | Sends a bulk of clock ticks (1 to 16) 81 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-raw-wire-mode-guide#bulk-clock-ticks-0b0010xxxx 82 | 83 | :param num: Number of clock ticks to send 84 | :type num: int 85 | """ 86 | if not num > 0: 87 | raise ValueError("Send at least one clock tick") 88 | if not num <= 16: 89 | raise ValueError("Too many ticks to send") 90 | 91 | CMD = 0b00100000 92 | CMD = CMD | (num - 1) 93 | 94 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 95 | 96 | if self._hydrabus.read(1) == b"\x01": 97 | return True 98 | else: 99 | self._logger.error("Error sending clocks.") 100 | return False 101 | 102 | def clocks(self, num): 103 | """ 104 | Sends a number of clock ticks 105 | 106 | :param num: Number of clock ticks to send 107 | :type num: int 108 | """ 109 | if not num > 0: 110 | raise ValueError("Must be a positive integer") 111 | 112 | while num > 16: 113 | self.bulk_ticks(16) 114 | num = num - 16 115 | self.bulk_ticks(num) 116 | 117 | def bulk_write(self, data=b""): 118 | """ 119 | Bulk write on Raw-Wire 120 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-raw-wire-mode-guide#bulk-raw-wire-transfer-0b0001xxxx 121 | 122 | :param data: Data to be sent 123 | :type data: bytes 124 | """ 125 | CMD = 0b00010000 126 | if not len(data) > 0: 127 | raise ValueError("Send at least one byte") 128 | if not len(data) <= 16: 129 | raise ValueError("Too many bytes to write") 130 | CMD = CMD | (len(data) - 1) 131 | 132 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 133 | self._hydrabus.write(data) 134 | 135 | if self._hydrabus.read(1) != b"\x01": 136 | self._logger.warn("Unknown error.") 137 | 138 | return self._hydrabus.read(len(data)) 139 | 140 | def set_speed(self, speed): 141 | """ 142 | Sets the clock max speed. 143 | 144 | :param speed: speed in Hz. Possible values : TODO 145 | """ 146 | speeds = {5000: 0b00, 50000: 0b01, 100_000: 0b10, 1_000_000: 0b11} 147 | if speed not in speeds.keys(): 148 | raise ValueError(f"Incorrect value. use {speeds.keys()}") 149 | CMD = 0b01100000 150 | CMD = CMD | speeds[speed] 151 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 152 | if self._hydrabus.read(1) == b"\x01": 153 | return True 154 | else: 155 | self._logger.error("Error setting speed.") 156 | return False 157 | 158 | def _configure_port(self): 159 | CMD = 0b10000000 160 | CMD = CMD | self._config 161 | 162 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 163 | if self._hydrabus.read(1) == b"\x01": 164 | return True 165 | else: 166 | self._logger.error("Error setting config.") 167 | return False 168 | 169 | def write_bits(self, data=b"", num_bits=0): 170 | """ 171 | Write bits on Raw-Wire bus 172 | Bits are sent MSB first. 173 | 174 | :param data: data to be sent 175 | :type data: bytes 176 | :param num_bits: number of bits to send 177 | :type data: int 178 | :return: Read bytes 179 | :rtype: bytes 180 | """ 181 | i = 0 182 | 183 | while num_bits > 0: 184 | CMD = 0b00110000 185 | if num_bits > 8: 186 | CMD = CMD | 7 187 | num_bits = num_bits - 8 188 | else: 189 | CMD = CMD | num_bits-1 190 | num_bits = 0 191 | 192 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 193 | self._hydrabus.write(data[i].to_bytes(1, byteorder="big")) 194 | if self._hydrabus.read(1) == b"\x01": 195 | return True 196 | else: 197 | self._logger.error("Error writing bits.") 198 | return False 199 | 200 | def write(self, data=b""): 201 | """ 202 | Write on Raw-Wire bus 203 | 204 | :param data: data to be sent 205 | :type data: bytes 206 | :return: Read bytes 207 | :rtype: bytes 208 | """ 209 | result = b"" 210 | for chunk in split(data, 16): 211 | result += self.bulk_write(chunk) 212 | return result 213 | 214 | def read(self, length=0): 215 | """ 216 | Read on Raw-Wire bus 217 | 218 | :param length: Number of bytes to read 219 | :type length: int 220 | :return: Read data 221 | :rtype: bytes 222 | """ 223 | result = b"" 224 | for _ in range(length): 225 | result += self.read_byte() 226 | 227 | return result 228 | 229 | @property 230 | def clk(self): 231 | """ 232 | CLK pin status 233 | """ 234 | return self._clk 235 | 236 | @clk.setter 237 | def clk(self, value): 238 | value = value & 1 239 | CMD = 0b00001010 240 | CMD = CMD | value 241 | 242 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 243 | if self._hydrabus.read(1) == b"\x01": 244 | self._clk = value 245 | return True 246 | else: 247 | self._logger.error("Error setting pin.") 248 | return False 249 | 250 | @property 251 | def sda(self): 252 | """ 253 | SDA pin status 254 | """ 255 | CMD = 0b00001000 256 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 257 | return int.from_bytes(self._hydrabus.read(1), byteorder="big") 258 | 259 | @sda.setter 260 | def sda(self, value): 261 | value = value & 1 262 | CMD = 0b00001100 263 | CMD = CMD | value 264 | 265 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 266 | if self._hydrabus.read(1) == b"\x01": 267 | self._clk = value 268 | return True 269 | else: 270 | self._logger.error("Error setting pin.") 271 | return False 272 | 273 | @property 274 | def polarity(self): 275 | """ 276 | Clock polarity (0=idle low, 1=idle high) 277 | """ 278 | return (self._config & 0b1) 279 | 280 | @polarity.setter 281 | def polarity(self, value): 282 | if value == 0: 283 | self._config = self._config & ~(1) 284 | self._configure_port() 285 | return True 286 | elif value == 1: 287 | self._config = self._config | 1 288 | self._configure_port() 289 | return True 290 | else: 291 | self._logger.error("Incorrect value. Must be 0 or 1") 292 | 293 | @property 294 | def wires(self): 295 | """ 296 | Raw-Wire mode (2=2-Wire, 3=3-Wire) 297 | """ 298 | if self._config & 0b100 == 0: 299 | return 2 300 | else: 301 | return 3 302 | 303 | @wires.setter 304 | def wires(self, value): 305 | if value == 2: 306 | self._config = self._config & ~(1 << 2) 307 | self._configure_port() 308 | return True 309 | elif value == 3: 310 | self._config = self._config | (1 << 2) 311 | self._configure_port() 312 | return True 313 | else: 314 | self._logger.error("Incorrect value. Must be 2 or 3") 315 | 316 | @property 317 | def gpio_mode(self): 318 | """ 319 | Raw-Wire GPIO mode (0=Push-Pull, 1=Open Drain) 320 | """ 321 | return (self._config & 0b1000) >> 3 322 | 323 | @gpio_mode.setter 324 | def gpio_mode(self, value): 325 | if value == 0: 326 | self._config = self._config & ~(1 << 3) 327 | self._configure_port() 328 | return True 329 | elif value == 1: 330 | self._config = self._config | (1 << 3) 331 | self._configure_port() 332 | return True 333 | else: 334 | self._logger.error("Incorrect value. Must be 0 or 1") 335 | -------------------------------------------------------------------------------- /pyHydrabus/sdio.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | from .common import split 18 | 19 | 20 | class SDIO(Protocol): 21 | """ 22 | SDIO protocol handler 23 | 24 | :example: 25 | 26 | >>> import pyHydrabus 27 | >>> m=pyHydrabus.SDIO('/dev/hydrabus') 28 | >>> # CMD0 - IDLE 29 | >>> s.send_no(0,0) 30 | 31 | >>> #CMD8 - OP_COND 32 | >>> s.send_short(8, 0x000001aa) 33 | 34 | >>> #ACMD41 *2 35 | >>> s.send_short(55,0) 36 | >>> s.send_short(41, 0x50ff8000) 37 | >>> s.send_short(55,0) 38 | >>> s.send_short(41, 0x50ff8000) 39 | 40 | >>> #CMD2 - GET CID 41 | >>> cid = s.send_long(2, 0) 42 | >>> print(f"CID: {cid.hex()}") 43 | 44 | >>> #CMD9 - RADDR 45 | >>> raddr = int.from_bytes(s.send_short(3, 0), byteorder='little') 46 | >>> print(f"RADDR: {raddr:08x}") 47 | 48 | >>> #CSD 49 | >>> csd = s.send_long(9, raddr) 50 | >>> print(f"CSD: {csd.hex()}") 51 | 52 | >>> #Select card 53 | >>> print(s.send_short(7, raddr).hex()) 54 | 55 | >>> #Get status 56 | >>> print(s.send_short(13, raddr).hex()) 57 | 58 | """ 59 | 60 | __SDIO_DEFAULT_CONFIG = 0b0 61 | 62 | def __init__(self, port=""): 63 | self._config = self.__SDIO_DEFAULT_CONFIG 64 | super().__init__(name=b"SDI1", fname="SDIO", mode_byte=b"\x0e", port=port) 65 | 66 | def _configure_port(self): 67 | CMD = 0b10000000 68 | CMD = CMD | self._config 69 | 70 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 71 | if self._hydrabus.read(1) == b"\x01": 72 | return True 73 | else: 74 | self._logger.error("Error setting config.") 75 | return False 76 | 77 | @property 78 | def bus_width(self): 79 | """ 80 | Data bus width (1 or 4) 81 | """ 82 | if self._config & 0b1: 83 | return 4 84 | else: 85 | return 1 86 | 87 | @bus_width.setter 88 | def bus_width(self, value): 89 | if value == 1: 90 | self._config = self._config & ~(1) 91 | elif value == 4: 92 | self._config = self._config | (1) 93 | else: 94 | print("Invalid value (1 or 4)") 95 | self._configure_port() 96 | 97 | @property 98 | def frequency(self): 99 | """ 100 | Select SDIO clock frequency 101 | 0: Slow (400kHz) 102 | 1: Fast (24MHz) 103 | """ 104 | if self._config & 0b10: 105 | return 1 106 | else: 107 | return 0 108 | 109 | @frequency.setter 110 | def frequency(self, value): 111 | if value == 0: 112 | self._config = self._config & ~(1<<1) 113 | elif value == 1: 114 | self._config = self._config | (1<<1) 115 | else: 116 | print("Invalid value (0 or 1)") 117 | self._configure_port() 118 | 119 | def send_no(self, cmd_id, cmd_arg): 120 | """ 121 | Send SDIO command with no reply from card 122 | 123 | :param cmd_id: Command ID (1 byte) 124 | :type cmd_id: int 125 | :param cmd_arg: Command argument (4 bytes) 126 | :type cmd_id: int 127 | """ 128 | CMD = 0b00000100 129 | self._hydrabus.write(CMD.to_bytes(1, byteorder="little")) 130 | self._hydrabus.write(cmd_id.to_bytes(1, byteorder="little")) 131 | self._hydrabus.write(cmd_arg.to_bytes(4, byteorder="little")) 132 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 133 | return status == 1 134 | 135 | def send_short(self, cmd_id, cmd_arg): 136 | """ 137 | Send SDIO command with short reply from card 138 | 139 | :param cmd_id: Command ID (1 byte) 140 | :type cmd_id: int 141 | :param cmd_arg: Command argument (4 bytes) 142 | :type cmd_id: int 143 | 144 | :return: Reply status, None in case of error 145 | :rtype: bytes 146 | """ 147 | CMD = 0b00000100 148 | CMD = 0b00000101 149 | self._hydrabus.write(CMD.to_bytes(1, byteorder="little")) 150 | self._hydrabus.write(cmd_id.to_bytes(1, byteorder="little")) 151 | self._hydrabus.write(cmd_arg.to_bytes(4, byteorder="little")) 152 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 153 | if status == 1: 154 | return self._hydrabus.read(4) 155 | else: 156 | return None 157 | 158 | def send_long(self, cmd_id, cmd_arg): 159 | """ 160 | Send SDIO command with long reply from card 161 | 162 | :param cmd_id: Command ID (1 byte) 163 | :type cmd_id: int 164 | :param cmd_arg: Command argument (4 bytes) 165 | :type cmd_id: int 166 | 167 | :return: Reply status, None in case of error 168 | :rtype: bytes 169 | """ 170 | CMD = 0b00000110 171 | self._hydrabus.write(CMD.to_bytes(1, byteorder="little")) 172 | self._hydrabus.write(cmd_id.to_bytes(1, byteorder="little")) 173 | self._hydrabus.write(cmd_arg.to_bytes(4, byteorder="little")) 174 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 175 | if status == 1: 176 | return self._hydrabus.read(16) 177 | else: 178 | return None 179 | 180 | def write(self, cmd_id, cmd_arg, data): 181 | """ 182 | Write SDIO block 183 | 184 | :param cmd_id: Command ID (1 byte) 185 | :type cmd_id: int 186 | :param cmd_arg: Command argument (4 bytes) 187 | :type cmd_id: int 188 | :param data: Data to be written (512 bytes) 189 | :type data: bytes 190 | 191 | :return: Transaction status 192 | :rtype: Boolean 193 | """ 194 | CMD = 0b00001001 195 | 196 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 197 | self._hydrabus.write(cmd_id.to_bytes(1, byteorder="little")) 198 | self._hydrabus.write(cmd_arg.to_bytes(4, byteorder="little")) 199 | self._hydrabus.write(data) 200 | 201 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 202 | 203 | return status == 1 204 | 205 | def read(self, cmd_id, cmd_arg): 206 | """ 207 | Read SDIO block 208 | 209 | :param cmd_id: Command ID (1 byte) 210 | :type cmd_id: int 211 | :param cmd_arg: Command argument (4 bytes) 212 | :type cmd_id: int 213 | 214 | :return: Read data 215 | :rtype: bytes 216 | """ 217 | CMD = 0b00001101 218 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 219 | self._hydrabus.write(cmd_id.to_bytes(1, byteorder="little")) 220 | self._hydrabus.write(cmd_arg.to_bytes(4, byteorder="little")) 221 | status = int.from_bytes(self._hydrabus.read(1), byteorder="little") 222 | if status == 1: 223 | return self._hydrabus.read(512) 224 | else: 225 | return b'' 226 | -------------------------------------------------------------------------------- /pyHydrabus/smartcard.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from .protocol import Protocol 18 | 19 | 20 | class Smartcard(Protocol): 21 | """ 22 | Smartcard protocol handler 23 | 24 | :example: 25 | 26 | >>> #Read ATR from a smartcard 27 | >>> import pyHydrabus 28 | >>> sm=pyHydrabus.Smartcard('/dev/hydrabus') 29 | >>> sm.prescaler=12 30 | >>> sm.baud=9600 31 | >>> sm.rst=1;sm.rst=0;sm.read(1) 32 | 33 | """ 34 | 35 | def __init__(self, port=""): 36 | self._config = 0b0000 37 | self._rst = 1 38 | self._baud = 9600 39 | self._prescaler = 12 40 | self._guardtime = 16 41 | super().__init__(name=b"CRD1", fname="Smartcard", mode_byte=b"\x0b", port=port) 42 | self._configure_port() 43 | 44 | def _configure_port(self): 45 | CMD = 0b10000000 46 | CMD = CMD | self._config 47 | 48 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 49 | if self._hydrabus.read(1) == b"\x01": 50 | self.rst = self._rst 51 | return True 52 | else: 53 | self._logger.error("Error setting config.") 54 | return False 55 | 56 | def write_read(self, data=b"", read_len=0): 57 | """ 58 | Write-then-read operation 59 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-binary-SMARTCARD-mode-guide#write-then-read-operation-0b00000100 60 | 61 | :param data: Data to be sent 62 | :type param: bytes 63 | :param read_len: number of bytes to read 64 | :type read_len: int 65 | """ 66 | CMD = 0b00000100 67 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 68 | 69 | self._hydrabus.write(len(data).to_bytes(2, byteorder="big")) 70 | 71 | self._hydrabus.write(read_len.to_bytes(2, byteorder="big")) 72 | 73 | self._hydrabus.timeout = 0 74 | if self._hydrabus.read(1) == b"\x00": 75 | self._logger.error("Cannot execute command. Too many bytes requested ?") 76 | return None 77 | self._hydrabus.timeout = None 78 | 79 | self._hydrabus.write(data) 80 | 81 | if self._hydrabus.read(1) != b"\x01": 82 | self._logger.warn("Unknown error. Aborting") 83 | return None 84 | 85 | return self._hydrabus.read(read_len) 86 | 87 | def write(self, data=b""): 88 | """ 89 | Write on smartard 90 | 91 | :param data: data to be sent 92 | :type param: bytes 93 | """ 94 | self.write_read(data, read_len=0) 95 | 96 | def read(self, read_len=0): 97 | """ 98 | Read on smartard 99 | 100 | :param read_len: number of bytes to be read 101 | :type read_len: int 102 | """ 103 | return self.write_read(b"", read_len=read_len) 104 | 105 | def get_atr(self): 106 | """ 107 | Get smartcard ATR 108 | """ 109 | self._hydrabus.write(b'\x08') 110 | atr_length = int.from_bytes(self._hydrabus.read(1), byteorder='big') 111 | return self._hydrabus.read(atr_length) 112 | 113 | @property 114 | def prescaler(self): 115 | """ 116 | Prescaler value 117 | """ 118 | return self._prescaler 119 | 120 | @prescaler.setter 121 | def prescaler(self, value): 122 | CMD = 0b00000110 123 | 124 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 125 | self._hydrabus.write(value.to_bytes(1, byteorder="big")) 126 | 127 | if self._hydrabus.read(1) == b"\x01": 128 | self._prescaler = value 129 | return True 130 | else: 131 | self._logger.error("Error setting prescaler.") 132 | return False 133 | 134 | @property 135 | def guardtime(self): 136 | """ 137 | Guard time value 138 | """ 139 | return self._prescaler 140 | 141 | @guardtime.setter 142 | def guardtime(self, value): 143 | CMD = 0b00000111 144 | 145 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 146 | self._hydrabus.write(value.to_bytes(1, byteorder="big")) 147 | 148 | if self._hydrabus.read(1) == b"\x01": 149 | self._guardtime = value 150 | return True 151 | else: 152 | self._logger.error("Error setting guard time.") 153 | return False 154 | 155 | @property 156 | def rst(self): 157 | """ 158 | RST pin status 159 | """ 160 | return self._rst 161 | 162 | @rst.setter 163 | def rst(self, value): 164 | value = value & 1 165 | CMD = 0b00000010 166 | CMD = CMD | value 167 | 168 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 169 | if self._hydrabus.read(1) == b"\x01": 170 | self._rst = value 171 | return True 172 | else: 173 | self._logger.error("Error setting pin.") 174 | return False 175 | 176 | @property 177 | def baud(self): 178 | """ 179 | Baud rate 180 | """ 181 | return self._baud 182 | 183 | @baud.setter 184 | def baud(self, value): 185 | CMD = 0b01100000 186 | 187 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 188 | self._hydrabus.write(value.to_bytes(4, byteorder="big")) 189 | 190 | if self._hydrabus.read(1) == b"\x01": 191 | self._baud = value 192 | return True 193 | else: 194 | self._logger.error("Error setting pin.") 195 | return False 196 | 197 | @property 198 | def pullup(self): 199 | if self._config & 0b100: 200 | return 1 201 | else: 202 | return 0 203 | 204 | @pullup.setter 205 | def pullup(self, value): 206 | if value == 0: 207 | self._config = self._config & ~(1 << 2) 208 | else: 209 | self._config = self._config | (1 << 2) 210 | self._configure_port() 211 | -------------------------------------------------------------------------------- /pyHydrabus/spi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .protocol import Protocol 17 | 18 | 19 | class SPI(Protocol): 20 | """ 21 | SPI protocol handler 22 | 23 | :example: 24 | 25 | >>> import pyHydrabus 26 | >>> s = pyHydrabus.SPI() 27 | >>> s.set_speed(s.SPI1_SPEED_10M) 28 | >>> s.cs = 0 29 | >>> s.bulk_write(b'\x00\x00\x00\x01') 30 | >>> s.read(4) 31 | 32 | """ 33 | 34 | __SPI_DEFAULT_CONFIG = 0b011 35 | 36 | SPI1_SPEED_320K = 0b000 37 | SPI1_SPEED_650K = 0b001 38 | SPI1_SPEED_1M = 0b010 39 | SPI1_SPEED_2M = 0b011 40 | SPI1_SPEED_5M = 0b100 41 | SPI1_SPEED_10M = 0b101 42 | SPI1_SPEED_21M = 0b110 43 | SPI1_SPEED_42M = 0b111 44 | 45 | SPI2_SPEED_160K = 0b000 46 | SPI2_SPEED_320K = 0b001 47 | SPI2_SPEED_650K = 0b010 48 | SPI2_SPEED_1M = 0b011 49 | SPI2_SPEED_2M = 0b100 50 | SPI2_SPEED_5M = 0b101 51 | SPI2_SPEED_10M = 0b110 52 | SPI2_SPEED_21M = 0b111 53 | 54 | def __init__(self, port=""): 55 | self._config = self.__SPI_DEFAULT_CONFIG 56 | self._cs_val = 1 57 | super().__init__(name=b"SPI1", fname="SPI", mode_byte=b"\x01", port=port) 58 | self._configure_port() 59 | 60 | @property 61 | def cs(self): 62 | """ 63 | Chip-Select (CS) status getter 64 | """ 65 | return self._cs_val 66 | 67 | @cs.setter 68 | def cs(self, mode=0): 69 | """ 70 | Chip-Select (CS) status setter 71 | 72 | :param mode: CS pin status (0=low, 1=high) 73 | """ 74 | CMD = 0b00000010 75 | CMD = CMD | mode 76 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 77 | if self._hydrabus.read(1) == b"\x01": 78 | self._cs_val = mode 79 | else: 80 | self._logger.error("Error setting CS.") 81 | 82 | def bulk_write(self, data=b""): 83 | """ 84 | Bulk write on SPI bus 85 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-SPI-mode-guide#bulk-spi-transfer-0b0001xxxx 86 | 87 | :param data: Data to be sent 88 | :type data: bytes 89 | 90 | :return: Bytes read during the transfer 91 | :rtype: bytes 92 | """ 93 | CMD = 0b00010000 94 | if not len(data) > 0: 95 | raise ValueError("Send at least one byte") 96 | if not len(data) <= 16: 97 | raise ValueError("Too many bytes to write") 98 | CMD = CMD | (len(data) - 1) 99 | 100 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 101 | if self._hydrabus.read(1) != b"\x01": 102 | self._logger.warn("Unknown error.") 103 | return None 104 | 105 | self._hydrabus.write(data) 106 | 107 | return self._hydrabus.read(len(data)) 108 | 109 | def write_read(self, data=b"", read_len=0, drive_cs=0): 110 | """ 111 | Write-then-read operation 112 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-SPI-mode-guide#write-then-read-operation-0b00000100---0b00000101 113 | 114 | :param data: Data to be sent 115 | :type data: bytes 116 | :param read_len: Number of bytes to read 117 | :type read_len: int 118 | :param drive_cs: Whether to enable chip select before writing/reading (0=yes, 1=no) 119 | :type drive_cs: int 120 | :return: Read data 121 | :rtype: bytes 122 | """ 123 | CMD = 0b00000100 124 | CMD = CMD | drive_cs 125 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 126 | 127 | self._hydrabus.write(len(data).to_bytes(2, byteorder="big")) 128 | 129 | self._hydrabus.write(read_len.to_bytes(2, byteorder="big")) 130 | 131 | self._hydrabus.timeout = 0 132 | ret = self._hydrabus.read(1) 133 | self._hydrabus.timeout = None 134 | if ret == b"\x00": 135 | self._logger.error("Cannot execute command. Too many bytes requested ?") 136 | return None 137 | elif ret == b"\x01" and len(data) == 0: 138 | return self._hydrabus.read(read_len) 139 | elif ret == b"": 140 | # No response, we can send data 141 | self._hydrabus.write(data) 142 | 143 | ret = self._hydrabus.read(1) 144 | if ret != b"\x01": 145 | self._logger.error("Transmit error") 146 | return None 147 | 148 | return self._hydrabus.read(read_len) 149 | else: 150 | self._logger.error(f"Unknown error") 151 | 152 | def write(self, data=b"", drive_cs=0): 153 | """ 154 | Write on SPI bus 155 | 156 | :param data: data to be sent 157 | :type data: bytes 158 | :param drive_cs: Whether to enable chip select before writing/reading (0=yes, 1=no) 159 | :type drive_cs: int 160 | """ 161 | self.write_read(data, read_len=0, drive_cs=drive_cs) 162 | 163 | def read(self, read_len=0, drive_cs=0): 164 | """ 165 | Read on SPI bus 166 | 167 | :param read_len: Number of bytes to be read 168 | :type read_len: int 169 | :param drive_cs: Whether to enable chip select before writing/reading (0=yes, 1=no) 170 | :type drive_cs: int 171 | :return: Read data 172 | :rtype: bytes 173 | """ 174 | result = b"" 175 | if drive_cs == 0: 176 | self.cs = 0 177 | while read_len > 0: 178 | if read_len >= 16: 179 | to_read = 16 180 | else: 181 | to_read = read_len 182 | result += self.bulk_write(b"\xff" * to_read) 183 | read_len -= to_read 184 | if drive_cs == 0: 185 | self.cs = 1 186 | return result 187 | 188 | def set_speed(self, speed): 189 | """ 190 | Set SPI bus speed 191 | 192 | :param speed: Select any of the defined speeds (SPI_SPEED_*) 193 | """ 194 | if not speed <= 0b111: 195 | raise ValueError("Incorrect speed") 196 | CMD = 0b01100000 197 | CMD = CMD | speed 198 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 199 | 200 | if self._hydrabus.read(1) == b"\x01": 201 | return True 202 | else: 203 | self._logger.error("Error setting speed.") 204 | return False 205 | 206 | def _configure_port(self): 207 | CMD = 0b10000000 208 | CMD = CMD | self._config 209 | 210 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 211 | if self._hydrabus.read(1) == b"\x01": 212 | return True 213 | else: 214 | self._logger.error("Error setting config.") 215 | return False 216 | 217 | @property 218 | def polarity(self): 219 | """ 220 | SPI polarity 221 | """ 222 | if self._config & 0b100: 223 | return 1 224 | else: 225 | return 0 226 | 227 | @polarity.setter 228 | def polarity(self, value): 229 | if value == 0: 230 | self._config = self._config & ~(1 << 2) 231 | else: 232 | self._config = self._config | (1 << 2) 233 | self._config & 0b111 234 | self._configure_port() 235 | 236 | @property 237 | def phase(self): 238 | """ 239 | SPI clock phase 240 | """ 241 | if self._config & 0b10: 242 | return 1 243 | else: 244 | return 0 245 | 246 | @phase.setter 247 | def phase(self, value): 248 | if value == 0: 249 | self._config = self._config & ~(1 << 1) 250 | else: 251 | self._config = self._config | (1 << 1) 252 | self._config & 0b111 253 | self._configure_port() 254 | 255 | @property 256 | def device(self): 257 | """ 258 | SPI device to use (1=spi1, 0=spi2) 259 | """ 260 | if self._config & 0b1: 261 | return 1 262 | else: 263 | return 0 264 | 265 | @device.setter 266 | def device(self, value): 267 | self._config = self.__SPI_DEFAULT_CONFIG 268 | if value == 0: 269 | self._config = self._config & ~(1 << 0) 270 | else: 271 | self._config = self._config | (1 << 0) 272 | self._configure_port() 273 | -------------------------------------------------------------------------------- /pyHydrabus/swd.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .rawwire import RawWire 17 | 18 | 19 | class SWD(RawWire): 20 | """ 21 | SWD protocol handler 22 | 23 | :example: 24 | 25 | >>> import pyHydrabus 26 | >>> swd = pyHydrabus.SWD('/dev/ttyACM0') 27 | >>> swd.bus_init() 28 | >>> swd.read_dp(0) 29 | >>> swd.write_dp(4, 0x50000000) 30 | >>> swd.scan_bus() 31 | 32 | """ 33 | 34 | def __init__(self, port=""): 35 | super().__init__(port) 36 | 37 | self._config = 0xA 38 | self._configure_port() 39 | 40 | def _apply_dp_parity(self, value): 41 | tmp = value & 0b00011110 42 | if (bin(tmp).count("1") % 2) == 1: 43 | value = value | 1 << 5 44 | return value 45 | 46 | def _sync(self): 47 | self.write(b"\x00") 48 | 49 | def bus_init(self): 50 | """ 51 | Initiate SWD bus. 52 | Sends the JTAG-TO-SWD token and sync clocks 53 | """ 54 | self.write( 55 | b"\xff\xff\xff\xff\xff\xff\x7b\x9e\xff\xff\xff\xff\xff\xff\x0f" 56 | ) 57 | self._sync() 58 | 59 | def multidrop_init(self, addr=0): 60 | """ 61 | Initialize a multidrop bus and select the DP at address 62 | 63 | :param addr: DP register address 64 | :type addr: int 65 | """ 66 | 67 | self.bus_init() 68 | 69 | # Send dormant to active command per ADIv6 70 | self.write(b"\x92\xf3\x09\x62\x95\x2d\x85\x86\xe9\xaf\xdd\xe3\xa2\x0e\xbc\x19") 71 | self.write_bits(b'\x00', 4) 72 | # Protocol activation code = SWD 73 | self.write(b"\x1a") 74 | # Bus reset 75 | self.write(b"\xff"*7) 76 | self._sync() 77 | # Finally, select DP 78 | self.write_dp(0xc, addr, ignore_status=True) 79 | 80 | def read_dp(self, addr, to_ap=0): 81 | """ 82 | Read a register from DP 83 | 84 | :param addr: DP register address 85 | :type addr: int 86 | 87 | :return: Value stored in register 88 | :rtype: int 89 | 90 | :example: 91 | 92 | >>> # read RDBUFF 93 | >>> swd.read_dp(0xc) 94 | """ 95 | CMD = 0x85 96 | CMD = CMD | to_ap << 1 97 | CMD = CMD | (addr & 0b1100) << 1 98 | CMD = self._apply_dp_parity(CMD) 99 | 100 | self.write(CMD.to_bytes(1, byteorder="little")) 101 | status = 0 102 | for i in range(3): 103 | status += ord(self.read_bit()) << i 104 | if status == 1: 105 | retval = int.from_bytes(self.read(4), byteorder="little") 106 | self._sync() 107 | return retval 108 | elif status == 2: 109 | # When receiving WAIT, retry transaction 110 | self._sync() 111 | self.write_dp(0, 0x0000001F) 112 | return self.read_dp(addr, to_ap) 113 | else: 114 | self._sync() 115 | raise ValueError(f"Returned status is {hex(status)}") 116 | 117 | def write_dp(self, addr, value, to_ap=0, ignore_status=False): 118 | """ 119 | Write to DP register 120 | 121 | :param addr: DP register address 122 | :type addr: int 123 | :param value: Value to be written to register 124 | :type value: int 125 | 126 | :example: 127 | 128 | >>> write_dp(4, 0x50000000) 129 | """ 130 | CMD = 0x81 131 | CMD = CMD | to_ap << 1 132 | CMD = CMD | (addr & 0b1100) << 1 133 | CMD = self._apply_dp_parity(CMD) 134 | 135 | self.write(CMD.to_bytes(1, byteorder="little")) 136 | status = 0 137 | for i in range(3): 138 | status += ord(self.read_bit()) << i 139 | self.clocks(2) 140 | 141 | if ignore_status == False: 142 | if status == 2: 143 | # When receiving WAIT, retry transaction 144 | self._sync() 145 | self.write_dp(0, 0x0000001F) 146 | return self.write_dp(addr, value, to_ap) 147 | if status != 1: 148 | self._sync() 149 | raise ValueError(f"Returned status is {hex(status)}") 150 | 151 | self.write(value.to_bytes(4, byteorder="little")) 152 | 153 | # Send the parity but along with the sync clocks 154 | if (bin(value).count("1") % 2) == 1: 155 | self.write(b"\x01") 156 | else: 157 | self.write(b"\x00") 158 | 159 | def read_ap(self, address, bank): 160 | """ 161 | Read AP register 162 | 163 | :param address: AP address on the bus 164 | :type address: int 165 | :param bank: AP register address 166 | :type bank: int 167 | 168 | :return: Value read from AP 169 | :rtype: int 170 | 171 | :example: 172 | 173 | >>> # Read AP IDR 174 | >>> read_ap(0, 0xfc) 175 | 176 | """ 177 | 178 | select_reg = 0 179 | # Place AP address in DP SELECT register 180 | select_reg = select_reg | address << 24 181 | # Place bank in register as well 182 | select_reg = select_reg | (bank & 0b11110000) 183 | # Write the SELECT DP register 184 | self.write_dp(8, select_reg) 185 | self.read_dp((bank & 0b1100), to_ap=1) 186 | # Read RDBUFF 187 | return self.read_dp(0xC) 188 | 189 | def write_ap(self, address, bank, value): 190 | """ 191 | Write to AP register 192 | 193 | :param address: AP address on the bus 194 | :type address: int 195 | :param bank: AP register address 196 | :type bank: int 197 | :param value: Value to be written to register 198 | :type value: int 199 | 200 | :example: 201 | 202 | >>> write_ap(0, 0x4, 0x20000000) 203 | """ 204 | 205 | select_reg = 0 206 | # Place AP address in DP SELECT register 207 | select_reg = select_reg | address << 24 208 | # Place bank in register as well 209 | select_reg = select_reg | (bank & 0b11110000) 210 | # Write the SELECT DP register 211 | self.write_dp(8, select_reg) 212 | # Send the actual value to the AP 213 | self.write_dp((bank & 0b1100), value, to_ap=1) 214 | 215 | def scan_bus(self): 216 | """ 217 | Scan the SWD bus for APs 218 | The SWD bus must have been enabled before using this command. 219 | """ 220 | 221 | for ap in range(256): 222 | idr = self.read_ap(ap, 0xFC) 223 | if idr != 0x0 and idr != 0xFFFFFFFF: 224 | print(f"0x{ap:02x}: 0x{idr:08x}") 225 | 226 | def abort(self, flags=0b11111): 227 | """ 228 | Abort AP transaction 229 | 230 | :param flags: Value to write to ABORT register 231 | :type flags: int 232 | """ 233 | self.write_dp(0, flags) 234 | -------------------------------------------------------------------------------- /pyHydrabus/uart.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | 17 | from .protocol import Protocol 18 | from .common import split 19 | 20 | 21 | class UART(Protocol): 22 | """ 23 | UART protocol handler 24 | 25 | :example: TODO 26 | 27 | >>> #Read data from an EEPROM 28 | >>> import pyHydrabus 29 | >>> u=pyHydrabus.UART('/dev/hydrabus') 30 | >>> u.baud=115200 31 | >>> u.echo=1 32 | 33 | """ 34 | 35 | PARITY_NONE = 0b00 36 | PARITY_EVEN = 0b01 37 | PARITY_ODD = 0b10 38 | 39 | def __init__(self, port=""): 40 | self._config = 0b0000 41 | self._echo = 0 42 | self._baud = 9600 43 | self._parity = self.PARITY_NONE 44 | super().__init__(name=b"ART1", fname="UART", mode_byte=b"\x03", port=port) 45 | 46 | def bulk_write(self, data=b""): 47 | """ 48 | Bulk write on UART 49 | https://github.com/hydrabus/hydrafw/wiki/HydraFW-Binary-I2C-mode-guide#bulk-i2c-write-0b0001xxxx FIXME: change link 50 | 51 | :param data: Data to be sent 52 | :type data: bytes 53 | 54 | :return: Returns the ACK status of the written bytes (b'\x00'=ACK, b'\x01'=NACK) 55 | :rtype: list 56 | """ 57 | CMD = 0b00010000 58 | if not len(data) > 0: 59 | raise ValueError("Send at least one byte") 60 | if not len(data) <= 16: 61 | raise ValueError("Too many bytes to write") 62 | CMD = CMD | (len(data) - 1) 63 | 64 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 65 | 66 | self._hydrabus.write(data) 67 | 68 | for _ in range(len(data)): 69 | if self._hydrabus.read(1) != b"\x01": 70 | self._logger.warn("Transfer error.") 71 | 72 | def write(self, data=b""): 73 | """ 74 | Write on UART bus 75 | 76 | :param data: data to be sent 77 | :type data: bytes 78 | """ 79 | for chunk in split(data, 16): 80 | self.bulk_write(chunk) 81 | 82 | @property 83 | def echo(self): 84 | """ 85 | Local echo (0=No, 1=Yes) 86 | """ 87 | return self._echo 88 | 89 | @echo.setter 90 | def echo(self, value): 91 | value = value & 1 92 | self._echo = value 93 | CMD = 0b00000010 94 | CMD = CMD | (not value) 95 | 96 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 97 | if self._hydrabus.read(1) == b"\x01": 98 | self._rst = value 99 | return True 100 | else: 101 | self._logger.error("Error setting echo.") 102 | return False 103 | 104 | def read(self, length=1): 105 | """ 106 | Read echoed data 107 | 108 | :param length: Number of bytes to read 109 | :type length: int 110 | 111 | :return: read bytes 112 | :rtype: bytes 113 | """ 114 | 115 | return self._hydrabus.read(length) 116 | 117 | @property 118 | def baud(self): 119 | """ 120 | Baud rate 121 | """ 122 | return self._baud 123 | 124 | @baud.setter 125 | def baud(self, value): 126 | CMD = 0b00000111 127 | 128 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 129 | self._hydrabus.write(value.to_bytes(4, byteorder="big")) 130 | 131 | if self._hydrabus.read(1) == b"\x01": 132 | self._baud = value 133 | return True 134 | else: 135 | self._logger.error("Error setting baudrate.") 136 | return False 137 | 138 | @property 139 | def parity(self): 140 | """ 141 | Parity 142 | """ 143 | return self._parity 144 | 145 | @parity.setter 146 | def parity(self, parity): 147 | CMD = 0b10000000 148 | 149 | CMD = CMD | (parity << 2) 150 | 151 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 152 | 153 | if self._hydrabus.read(1) == b"\x01": 154 | self._parity = parity 155 | return True 156 | else: 157 | self._logger.error("Error setting parity.") 158 | return False 159 | 160 | def bridge(self): 161 | """ 162 | Bind the Hydrabus USB and UART 163 | Note that in order to leave bridge mode, you need to press the UBTN 164 | """ 165 | CMD = 0b00001111 166 | self._hydrabus.write(CMD.to_bytes(1, byteorder="big")) 167 | -------------------------------------------------------------------------------- /pyHydrabus/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | from .hydrabus import Hydrabus 17 | 18 | 19 | class Utils: 20 | """ 21 | Utilities available in hydrafw 22 | 23 | :param port: The port name 24 | :type port: str 25 | """ 26 | 27 | def __init__(self, port=""): 28 | self._hydrabus = Hydrabus(port) 29 | self._logger = logging.getLogger(__name__) 30 | 31 | self._hydrabus.flush_input() 32 | 33 | @property 34 | def adc(self): 35 | """ 36 | Read ADC value 37 | 38 | :return: ADC value (10 bits) 39 | :rtype: int 40 | """ 41 | self._hydrabus.write(b"\x14") 42 | v = self._hydrabus.read(2) 43 | return int.from_bytes(v, byteorder="big") 44 | 45 | def continuous_adc(self): 46 | """ 47 | Continuously print ADC value 48 | """ 49 | try: 50 | self._hydrabus.write(b"\x15") 51 | while 1: 52 | v = self._hydrabus.read(2) 53 | except KeyboardInterrupt: 54 | self._hydrabus.write(b"\x00") 55 | self._hydrabus.reset() 56 | return True 57 | 58 | def frequency(self): 59 | """ 60 | Read frequency value 61 | 62 | :return: (frequency, duty cycle) 63 | :rtype: tuple 64 | """ 65 | self._hydrabus.write(b"\x16") 66 | freq = self._hydrabus.read(4) 67 | duty = self._hydrabus.read(4) 68 | return ( 69 | int.from_bytes(freq, byteorder="little"), 70 | int.from_bytes(duty, byteorder="little"), 71 | ) 72 | 73 | def close(self): 74 | """ 75 | Close the communication channel and resets Hydrabus 76 | """ 77 | self._hydrabus.exit_bbio() 78 | self._hydrabus.close() 79 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pyHydrabus" 3 | dynamic = ["version"] 4 | dependencies = [ 5 | "pyserial", 6 | ] 7 | authors = [ 8 | {name = "Baldanos"}, 9 | ] 10 | maintainers = [ 11 | {name = "Baldanos"} 12 | ] 13 | description = "Hydrabus BBIO python bindings" 14 | readme = "README.md" 15 | license = {file = "LICENSE"} 16 | keywords = [ "Hydrabus" ] 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "License :: OSI Approved :: Apache Software License", 20 | "Operating System :: OS Independent", 21 | ] 22 | 23 | [project.urls] 24 | Homepage = "https://www.hydrabus.com" 25 | Documentation = "https://pyhydrabus.readthedocs.io" 26 | Repository = "https://github.com/hydrabus/pyHydrabus" 27 | 28 | 29 | [build-system] 30 | build-backend = "setuptools.build_meta" 31 | requires = [ 32 | "setuptools", 33 | "pyserial", 34 | ] 35 | 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | version = attr: pyHydrabus.__version__ 3 | 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Nicolas OBERLI 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import setuptools 16 | 17 | setuptools.setup() 18 | --------------------------------------------------------------------------------