├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── python-build-test.yml │ └── python-publish.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── doc ├── Makefile ├── conf.py ├── index.rst ├── make.bat └── requirements.txt ├── setup.cfg ├── setup.py ├── smbus2 ├── __init__.py ├── py.typed ├── smbus2.py └── smbus2.pyi └── tests ├── test_datatypes.py └── test_smbus2.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '39 16 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v5 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v3 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v3 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v3 68 | -------------------------------------------------------------------------------- /.github/workflows/python-build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | include: 14 | - python-version: "3.7" 15 | os: ubuntu-22.04 16 | - python-version: "3.8" 17 | os: ubuntu-22.04 18 | - python-version: "3.9" 19 | os: ubuntu-24.04 20 | - python-version: "3.10" 21 | os: ubuntu-latest 22 | - python-version: "3.11" 23 | os: ubuntu-latest 24 | - python-version: "3.12" 25 | os: ubuntu-latest 26 | - python-version: "3.13" 27 | os: ubuntu-latest 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - uses: actions/checkout@v5 31 | - name: Set up Python ${{ matrix.python-version }} 32 | uses: actions/setup-python@v6 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | - name: Install dependencies 36 | run: | 37 | python -m pip install --upgrade pip 38 | pip install flake8 pytest 39 | - name: Additional install for Python 3.12 or higher 40 | if: ${{ matrix.python-version >= '3.12' }} 41 | run: | 42 | pip install setuptools 43 | - name: Install smbus2 44 | run: | 45 | python setup.py install 46 | - name: Lint with flake8 47 | run: | 48 | # stop the build if there are Python syntax errors or undefined names 49 | flake8 . --count --show-source --statistics 50 | - name: Test with pytest 51 | run: | 52 | pytest --capture=no 53 | 54 | build-docs: 55 | runs-on: ubuntu-latest 56 | strategy: 57 | matrix: 58 | python-version: ["3.10"] 59 | steps: 60 | - uses: actions/checkout@v5 61 | - name: Set up Python ${{ matrix.python-version }} 62 | uses: actions/setup-python@v6 63 | with: 64 | python-version: ${{ matrix.python-version }} 65 | - name: Install dependencies 66 | run: | 67 | python -m pip install --upgrade pip 68 | pip install sphinx sphinx_rtd_theme 69 | - name: Build docs with Sphinx 70 | run: | 71 | cd doc 72 | sphinx-build -W -b html -d ./doctrees . ./html 73 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v5 17 | - name: Set up Python 18 | uses: actions/setup-python@v6 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USER }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_SECRET }} 29 | run: | 30 | python setup.py sdist 31 | python setup.py bdist_wheel --universal 32 | twine upload dist/* 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tox/ 2 | doc/_build 3 | doc/doctrees 4 | doc/html 5 | build/ 6 | dist/ 7 | .vscode/ 8 | .idea/ 9 | 10 | *.pyc 11 | *.egg-info 12 | __pycache__ 13 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | build: 8 | os: ubuntu-22.04 9 | tools: 10 | python: "3.12" 11 | 12 | python: 13 | install: 14 | - method: pip 15 | path: . 16 | - requirements: doc/requirements.txt 17 | 18 | sphinx: 19 | configuration: doc/conf.py 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | Notable changes to the smbus2 project are recorded here. 3 | 4 | ## [Unreleased] 5 | 6 | ## [0.5.0] - 2024-10-19 7 | - Slight change of `SMBus` object life-cycle to better handle "open -> close -> open" scenarios. [107](https://github.com/kplindegaard/smbus2/pull/107). 8 | - Maintenance updates: 9 | - Documentation builds again! [106](https://github.com/kplindegaard/smbus2/pull/106). 10 | - Typing corrections. [109](https://github.com/kplindegaard/smbus2/pull/109). 11 | - Python 3.12, 3.13 added. 12 | 13 | ## [0.4.3] - 2023-08-25 14 | - Build pipeline and test updates only: 15 | - Upgrade build pipelines 16 | - Added Python 3.11 17 | - Python 2.7, 3.4, and 3.5 no longer tested. 18 | - Update deprecated Sphinx config format. 19 | - Corrected deprecated `assertEquals` in the unit tests. 20 | 21 | ## [0.4.2] - 2022-06-05 22 | ### General 23 | - Explicitly export from top level of package [#69](https://github.com/kplindegaard/smbus2/pull/69). 24 | - Transitioned pipelines from Travis-CI to GitHub Actions. 25 | - Python 3.10 added. 26 | 27 | ## [0.4.1] - 2021-01-17 28 | ### General 29 | - SonarCloud quality checks. 30 | - Tests added to the dist package. 31 | 32 | ## [0.4.0] - 2020-12-05 33 | ### Added 34 | - Support for SMBus PEC (Packet Error Checking). 35 | - Support for Python 3 type hinting and mypy static type analysis. Type stubs added to the project. 36 | 37 | ### Removed 38 | As of this version the `SMBusWrapper` class is removed and must be replaced with `SMBus`: 39 | 40 | ```python 41 | # No longer valid! 42 | with SMBusWrapper(1) as bus: 43 | ... 44 | 45 | # Replace with 46 | with SMBus(1) as bus: 47 | ... 48 | ``` 49 | 50 | ### General 51 | - Added Python 3.8 and 3.9 to Travis-CI build config. 52 | - Cleaner docs: 53 | - Removed redundant `README.rst`. 54 | - `README.md`, `CHANGELOG.md` and `LICENSE` added to dist package. 55 | 56 | ## [0.3.0] - 2019-09-07 57 | ### Added 58 | - Missing SMBus commands added: `process_call`, `write_block_data`, `read_block_data`, `block_process_call`. 59 | Note that the latter two are normally not supported by pure i2c-devices. 60 | - SMBus.__init__(), SMBus.open(): `bus` can be a file path as well (issue #17). 61 | - Added enter/exit handler to `SMBus` class. 62 | - Expose enumeration of i2c funcs. Previously, smbus.funcs was defined, but its flags were not exported. All flags moved into the `I2cFunc` class and exported that. 63 | - Added convenience features making the `i2c_msg` class easier to work with. 64 | 65 | ### Deprecation warning 66 | - The `SMBusWrapper` class is now considered deprecated. Please replace with `SMBus`. 67 | 68 | ### Changed 69 | - Removed `__slots__` from `i2c_msg` class. 70 | - Whole `i2c_msg_iter` class replaced by a simple generator function with same functionality 71 | 72 | 73 | ## [0.2.3] - 2019-01-10 74 | ### Fixed 75 | - Incorrect `i2c_msg` created in Python 3.x if str input contains ascii chars >= 128. 76 | 77 | ## [0.2.2] - 2019-01-03 78 | ### Added 79 | - SMBus `write_quick` command. 80 | 81 | ## [0.2.1] - 2018-06-02 82 | ### Added 83 | - Ability to force individual r/w operations. 84 | - API docs available on readthedocs.org. 85 | 86 | ## [0.2.0] - 2017-08-19 87 | ### Added 88 | - I2C: Support for i2c_rdwr transactions. 89 | 90 | ## [0.1.5] - 2017-05-17 91 | ### Added 92 | - SMBus support for read and write single bytes without offset/register address. 93 | 94 | ## [0.1.4] - 2016-09-18 95 | ### Added 96 | - Force option for SMBusWrapper class. 97 | 98 | ## [0.1.3] - 2016-08-14 99 | ### Added 100 | - Flag flag for forcing address use even when a driver is already using it. 101 | ### Fixed bugs 102 | - Accept zero (0) as bus ID. 103 | - Save address when setting it. 104 | 105 | ## 0.1.2 - 2016-04-19 106 | First published version. 107 | 108 | 109 | [Unreleased]: https://github.com/kplindegaard/smbus2/compare/0.5.0...HEAD 110 | [0.5.0]: https://github.com/kplindegaard/smbus2/compare/0.4.3...0.5.0 111 | [0.4.3]: https://github.com/kplindegaard/smbus2/compare/0.4.2...0.4.3 112 | [0.4.2]: https://github.com/kplindegaard/smbus2/compare/0.4.1...0.4.2 113 | [0.4.1]: https://github.com/kplindegaard/smbus2/compare/0.4.0...0.4.1 114 | [0.4.0]: https://github.com/kplindegaard/smbus2/compare/0.3.0...0.4.0 115 | [0.3.0]: https://github.com/kplindegaard/smbus2/compare/0.2.3...0.3.0 116 | [0.2.3]: https://github.com/kplindegaard/smbus2/compare/0.2.2...0.2.3 117 | [0.2.2]: https://github.com/kplindegaard/smbus2/compare/0.2.1...0.2.2 118 | [0.2.1]: https://github.com/kplindegaard/smbus2/compare/0.2.0...0.2.1 119 | [0.2.0]: https://github.com/kplindegaard/smbus2/compare/0.1.5...0.2.0 120 | [0.1.5]: https://github.com/kplindegaard/smbus2/compare/0.1.4...0.1.5 121 | [0.1.4]: https://github.com/kplindegaard/smbus2/compare/0.1.3...0.1.4 122 | [0.1.3]: https://github.com/kplindegaard/smbus2/compare/0.1.2...0.1.3 123 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Karl-Petter Lindegaard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md 2 | include LICENSE 3 | include README.md 4 | graft tests 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smbus2 2 | A drop-in replacement for smbus-cffi/smbus-python in pure Python 3 | 4 | [![Build Status](https://github.com/kplindegaard/smbus2/actions/workflows/python-build-test.yml/badge.svg?branch=master)](https://github.com/kplindegaard/smbus2/actions/workflows/python-build-test.yml) 5 | [![Documentation Status](https://readthedocs.org/projects/smbus2/badge/?version=latest)](http://smbus2.readthedocs.io/en/latest/?badge=latest) 6 | ![CodeQL](https://github.com/kplindegaard/smbus2/actions/workflows/codeql-analysis.yml/badge.svg?branch=master) 7 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=kplindegaard_smbus2&metric=alert_status)](https://sonarcloud.io/dashboard?id=kplindegaard_smbus2) 8 | 9 | ![Python Verions](https://img.shields.io/pypi/pyversions/smbus2.svg) 10 | [![PyPi Version](https://img.shields.io/pypi/v/smbus2.svg)](https://pypi.org/project/smbus2/) 11 | [![PyPI - Downloads](https://img.shields.io/pypi/dm/smbus2)](https://pypi.org/project/smbus2/) 12 | 13 | # Introduction 14 | 15 | smbus2 is (yet another) pure Python implementation of the [python-smbus](http://www.lm-sensors.org/browser/i2c-tools/trunk/py-smbus/) package. 16 | 17 | It was designed from the ground up with two goals in mind: 18 | 19 | 1. It should be a drop-in replacement of smbus. The syntax shall be the same. 20 | 2. Use the inherent i2c structs and unions to a greater extent than other pure Python implementations like [pysmbus](https://github.com/bjornt/pysmbus) does. By doing so, it will be more feature complete and easier to extend. 21 | 22 | Currently supported features are: 23 | 24 | * Get i2c capabilities (I2C_FUNCS) 25 | * SMBus Packet Error Checking (PEC) support 26 | * read_byte 27 | * write_byte 28 | * read_byte_data 29 | * write_byte_data 30 | * read_word_data 31 | * write_word_data 32 | * read_i2c_block_data 33 | * write_i2c_block_data 34 | * write_quick 35 | * process_call 36 | * read_block_data 37 | * write_block_data 38 | * block_process_call 39 | * i2c_rdwr - *combined write/read transactions with repeated start* 40 | 41 | It is developed on Python 2.7 but works without any modifications in Python 3.X too. 42 | 43 | More information about updates and general changes are recorded in the [change log](https://github.com/kplindegaard/smbus2/blob/master/CHANGELOG.md). 44 | 45 | # SMBus code examples 46 | 47 | smbus2 installs next to smbus as the package, so it's not really a 100% replacement. You must change the module name. 48 | 49 | ## Example 1a: Read a byte 50 | 51 | ```python 52 | from smbus2 import SMBus 53 | 54 | # Open i2c bus 1 and read one byte from address 80, offset 0 55 | bus = SMBus(1) 56 | b = bus.read_byte_data(80, 0) 57 | print(b) 58 | bus.close() 59 | ``` 60 | 61 | ## Example 1b: Read a byte using 'with' 62 | 63 | This is the very same example but safer to use since the smbus will be closed automatically when exiting the with block. 64 | 65 | ```python 66 | from smbus2 import SMBus 67 | 68 | with SMBus(1) as bus: 69 | b = bus.read_byte_data(80, 0) 70 | print(b) 71 | ``` 72 | 73 | ## Example 1c: Read a byte with PEC enabled 74 | 75 | Same example with Packet Error Checking enabled. 76 | 77 | ```python 78 | from smbus2 import SMBus 79 | 80 | with SMBus(1) as bus: 81 | bus.pec = 1 # Enable PEC 82 | b = bus.read_byte_data(80, 0) 83 | print(b) 84 | ``` 85 | 86 | ## Example 2: Read a block of data 87 | 88 | You can read up to 32 bytes at once. 89 | 90 | ```python 91 | from smbus2 import SMBus 92 | 93 | with SMBus(1) as bus: 94 | # Read a block of 16 bytes from address 80, offset 0 95 | block = bus.read_i2c_block_data(80, 0, 16) 96 | # Returned value is a list of 16 bytes 97 | print(block) 98 | ``` 99 | 100 | ## Example 3: Write a byte 101 | 102 | ```python 103 | from smbus2 import SMBus 104 | 105 | with SMBus(1) as bus: 106 | # Write a byte to address 80, offset 0 107 | data = 45 108 | bus.write_byte_data(80, 0, data) 109 | ``` 110 | 111 | ## Example 4: Write a block of data 112 | 113 | It is possible to write 32 bytes at the time, but I have found that error-prone. Write less and add a delay in between if you run into trouble. 114 | 115 | ```python 116 | from smbus2 import SMBus 117 | 118 | with SMBus(1) as bus: 119 | # Write a block of 8 bytes to address 80 from offset 0 120 | data = [1, 2, 3, 4, 5, 6, 7, 8] 121 | bus.write_i2c_block_data(80, 0, data) 122 | ``` 123 | 124 | # I2C 125 | 126 | Starting with v0.2, the smbus2 library also has support for combined read and write transactions. *i2c_rdwr* is not really a SMBus feature but comes in handy when the master needs to: 127 | 128 | 1. read or write bulks of data larger than SMBus' 32 bytes limit. 129 | 1. write some data and then read from the slave with a repeated start and no stop bit between. 130 | 131 | Each operation is represented by a *i2c_msg* message object. 132 | 133 | 134 | ## Example 5: Single i2c_rdwr 135 | 136 | ```python 137 | from smbus2 import SMBus, i2c_msg 138 | 139 | with SMBus(1) as bus: 140 | # Read 64 bytes from address 80 141 | msg = i2c_msg.read(80, 64) 142 | bus.i2c_rdwr(msg) 143 | 144 | # Write a single byte to address 80 145 | msg = i2c_msg.write(80, [65]) 146 | bus.i2c_rdwr(msg) 147 | 148 | # Write some bytes to address 80 149 | msg = i2c_msg.write(80, [65, 66, 67, 68]) 150 | bus.i2c_rdwr(msg) 151 | ``` 152 | 153 | ## Example 6: Dual i2c_rdwr 154 | 155 | To perform dual operations just add more i2c_msg instances to the bus call: 156 | 157 | ```python 158 | from smbus2 import SMBus, i2c_msg 159 | 160 | # Single transaction writing two bytes then read two at address 80 161 | write = i2c_msg.write(80, [40, 50]) 162 | read = i2c_msg.read(80, 2) 163 | with SMBus(1) as bus: 164 | bus.i2c_rdwr(write, read) 165 | ``` 166 | 167 | ## Example 7: Access i2c_msg data 168 | 169 | All data is contained in the i2c_msg instances. Here are some data access alternatives. 170 | 171 | ```python 172 | # 1: Convert message content to list 173 | msg = i2c_msg.write(60, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 174 | data = list(msg) # data = [1, 2, 3, ...] 175 | print(len(data)) # => 10 176 | 177 | # 2: i2c_msg is iterable 178 | for value in msg: 179 | print(value) 180 | 181 | # 3: Through i2c_msg properties 182 | for k in range(msg.len): 183 | print(msg.buf[k]) 184 | ``` 185 | 186 | # Installation instructions 187 | 188 | From [PyPi](https://pypi.org/) with `pip`: 189 | 190 | ``` 191 | pip install smbus2 192 | ``` 193 | 194 | From [conda-forge](https://anaconda.org/conda-forge) using `conda`: 195 | 196 | ``` 197 | conda install -c conda-forge smbus2 198 | ``` 199 | 200 | Installation from source code is straight forward: 201 | 202 | ``` 203 | python setup.py install 204 | ``` 205 | -------------------------------------------------------------------------------- /doc/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 | SPHINXPROJ = smbus2 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # The MIT License (MIT) 4 | # Copyright (c) 2020 Karl-Petter Lindegaard 5 | # 6 | # smbus2 documentation build configuration file. 7 | # 8 | # This file is execfile()d with the current directory set to its 9 | # containing dir. 10 | # 11 | # Note that not all possible configuration values are present in this 12 | # autogenerated file. 13 | # 14 | # All configuration values have a default; values that are commented out 15 | # serve to show the default. 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | sys.path.insert(0, os.path.abspath('..')) 24 | 25 | from smbus2 import __version__ # noqa: E402 26 | 27 | 28 | # -- General configuration ------------------------------------------------ 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | # 32 | # needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | 'sphinx.ext.autodoc', 39 | 'sphinx.ext.intersphinx' 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # The suffix(es) of source filenames. 46 | # You can specify multiple suffix as a list of string: 47 | # 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = '.rst' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = 'smbus2' 56 | author = 'Karl-Petter Lindegaard' 57 | copyright = '2017, {}'.format(author) 58 | 59 | # The version info for the project you're documenting, acts as replacement for 60 | # |version| and |release|, also used in various other places throughout the 61 | # built documents. 62 | # 63 | # The short X.Y version. 64 | version = __version__ 65 | # The full version, including alpha/beta/rc tags. 66 | release = __version__ 67 | 68 | # The language for content autogenerated by Sphinx. Refer to documentation 69 | # for a list of supported languages. 70 | # 71 | # This is also used if you do content translation via gettext catalogs. 72 | # Usually you set "language" from the command line for these cases. 73 | language = 'en' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | # This patterns also effect to html_static_path and html_extra_path 78 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 79 | 80 | # The name of the Pygments (syntax highlighting) style to use. 81 | pygments_style = 'sphinx' 82 | 83 | # If true, `todo` and `todoList` produce output, else they produce nothing. 84 | todo_include_todos = False 85 | 86 | autodoc_default_options = { 87 | 'members': True, 88 | 'member-order': 'bysource', 89 | 'special-members': '__init__,__str__,__iter__,_get_funcs,_set_address', 90 | 'undoc-members': False, 91 | 'exclude-members': '__weakref__,addr,flags,len,buf', 92 | } 93 | 94 | # -- Options for HTML output ---------------------------------------------- 95 | 96 | # The theme to use for HTML and HTML Help pages. See the documentation for 97 | # a list of builtin themes. 98 | # 99 | html_theme = 'sphinx_rtd_theme' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | # 105 | # html_theme_options = {} 106 | 107 | # Add any paths that contain custom static files (such as style sheets) here, 108 | # relative to this directory. They are copied after the builtin static files, 109 | # so a file named "default.css" will overwrite the builtin "default.css". 110 | # html_static_path = ['_static'] 111 | 112 | # Custom sidebar templates, must be a dictionary that maps document names 113 | # to template names. 114 | # 115 | # This is required for the alabaster theme 116 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 117 | html_sidebars = { 118 | '**': [ 119 | 'relations.html', # needs 'show_related': True theme option to display 120 | 'searchbox.html', 121 | ] 122 | } 123 | 124 | 125 | # -- Options for HTMLHelp output ------------------------------------------ 126 | 127 | # Output file base name for HTML help builder. 128 | htmlhelp_basename = 'smbus2doc' 129 | 130 | 131 | # -- Options for LaTeX output --------------------------------------------- 132 | 133 | latex_elements = { 134 | # The paper size ('letterpaper' or 'a4paper'). 135 | # 136 | # 'papersize': 'letterpaper', 137 | 138 | # The font size ('10pt', '11pt' or '12pt'). 139 | # 140 | # 'pointsize': '10pt', 141 | 142 | # Additional stuff for the LaTeX preamble. 143 | # 144 | # 'preamble': '', 145 | 146 | # Latex figure (float) alignment 147 | # 148 | # 'figure_align': 'htbp', 149 | } 150 | 151 | # Grouping the document tree into LaTeX files. List of tuples 152 | # (source start file, target name, title, 153 | # author, documentclass [howto, manual, or own class]). 154 | latex_documents = [ 155 | (master_doc, 'smbus2.tex', 'smbus2 Documentation', 156 | author, 'manual'), 157 | ] 158 | 159 | 160 | # -- Options for manual page output --------------------------------------- 161 | 162 | # One entry per manual page. List of tuples 163 | # (source start file, name, description, authors, manual section). 164 | man_pages = [ 165 | (master_doc, 'smbus2', 'smbus2 Documentation', 166 | [author], 1) 167 | ] 168 | 169 | 170 | # -- Options for Texinfo output ------------------------------------------- 171 | 172 | # Grouping the document tree into Texinfo files. List of tuples 173 | # (source start file, target name, title, author, 174 | # dir menu entry, description, category) 175 | texinfo_documents = [ 176 | (master_doc, 'smbus2', 'smbus2 Documentation', 177 | author, 'smbus2', 'One line description of project.', 178 | 'Miscellaneous'), 179 | ] 180 | 181 | 182 | # Configuration for intersphinx: refer to the Python standard library. 183 | intersphinx_mapping = {'python': ('https://docs.python.org/3/', None)} 184 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | smbus2 2 | ====== 3 | .. image:: https://github.com/kplindegaard/smbus2/actions/workflows/python-build-test.yml/badge.svg?branch=master 4 | :target: https://github.com/kplindegaard/smbus2/actions/workflows/python-build-test.yml 5 | 6 | .. image:: https://img.shields.io/pypi/pyversions/smbus2.svg 7 | :target: https://pypi.python.org/pypi/smbus2 8 | 9 | .. image:: https://img.shields.io/pypi/v/smbus2.svg 10 | :target: https://pypi.python.org/pypi/smbus2 11 | 12 | .. automodule:: smbus2 13 | :members: SMBus, i2c_msg 14 | :undoc-members: 15 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=smbus2 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=7 2 | sphinx-rtd-theme 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [flake8] 5 | ignore = E121, E122, E124, E125, E127, E128, E241, E402, E501, E731, E722 6 | exclude = 7 | .ropeproject, 8 | .tox, 9 | .eggs, 10 | # No need to traverse our git directory 11 | .git, 12 | # There's no value in checking cache directories 13 | __pycache__, 14 | doc, 15 | build, 16 | dist 17 | 18 | [pycodestyle] 19 | max-line-length = 100 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2017 Karl-Petter Lindegaard 3 | 4 | import re 5 | import os 6 | from io import open 7 | from setuptools import setup 8 | 9 | 10 | def read_file(fname, encoding='utf-8'): 11 | with open(fname, encoding=encoding) as r: 12 | return r.read() 13 | 14 | 15 | def find_version(*file_paths): 16 | fpath = os.path.join(os.path.dirname(__file__), *file_paths) 17 | version_file = read_file(fpath) 18 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 19 | version_file, re.M) 20 | if version_match: 21 | return version_match.group(1) 22 | 23 | err_msg = 'Unable to find version string in {}'.format(fpath) 24 | raise RuntimeError(err_msg) 25 | 26 | 27 | README = read_file('README.md') 28 | version = find_version('smbus2', '__init__.py') 29 | 30 | setup( 31 | name="smbus2", 32 | version=version, 33 | author="Karl-Petter Lindegaard", 34 | author_email="kp.lindegaard@gmail.com", 35 | description="smbus2 is a drop-in replacement for smbus-cffi/smbus-python in pure Python", 36 | license="MIT", 37 | keywords=['smbus', 'smbus2', 'python', 'i2c', 'raspberrypi', 'linux'], 38 | url="https://github.com/kplindegaard/smbus2", 39 | packages=['smbus2'], 40 | package_data={'smbus2': ['py.typed', 'smbus2.pyi']}, 41 | long_description=README, 42 | long_description_content_type="text/markdown", 43 | extras_require={ 44 | 'docs': [ 45 | 'sphinx >= 1.5.3' 46 | ], 47 | 'qa': [ 48 | 'flake8' 49 | ] 50 | }, 51 | classifiers=[ 52 | "Development Status :: 4 - Beta", 53 | "Topic :: Utilities", 54 | "License :: OSI Approved :: MIT License", 55 | "Programming Language :: Python :: 2", 56 | "Programming Language :: Python :: 2.7", 57 | "Programming Language :: Python :: 3", 58 | "Programming Language :: Python :: 3.6", 59 | "Programming Language :: Python :: 3.7", 60 | "Programming Language :: Python :: 3.8", 61 | "Programming Language :: Python :: 3.9", 62 | "Programming Language :: Python :: 3.10", 63 | "Programming Language :: Python :: 3.11", 64 | "Programming Language :: Python :: 3.12", 65 | "Programming Language :: Python :: 3.13" 66 | ], 67 | ) 68 | -------------------------------------------------------------------------------- /smbus2/__init__.py: -------------------------------------------------------------------------------- 1 | """smbus2 - A drop-in replacement for smbus-cffi/smbus-python""" 2 | # The MIT License (MIT) 3 | # Copyright (c) 2020 Karl-Petter Lindegaard 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from .smbus2 import SMBus, i2c_msg, I2cFunc # noqa: F401 24 | 25 | __version__ = "0.5.0" 26 | __all__ = ["SMBus", "i2c_msg", "I2cFunc"] 27 | -------------------------------------------------------------------------------- /smbus2/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kplindegaard/smbus2/6c9f880446a9fc09d8b31aea5b1e84fbcc80e511/smbus2/py.typed -------------------------------------------------------------------------------- /smbus2/smbus2.py: -------------------------------------------------------------------------------- 1 | """smbus2 - A drop-in replacement for smbus-cffi/smbus-python""" 2 | # The MIT License (MIT) 3 | # Copyright (c) 2020 Karl-Petter Lindegaard 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import os 24 | import sys 25 | from fcntl import ioctl 26 | from ctypes import c_uint32, c_uint8, c_uint16, c_char, POINTER, Structure, Array, Union, create_string_buffer, string_at 27 | 28 | 29 | # Commands from uapi/linux/i2c-dev.h 30 | I2C_SLAVE = 0x0703 # Use this slave address 31 | I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if it is already in use by a driver! 32 | I2C_FUNCS = 0x0705 # Get the adapter functionality mask 33 | I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only) 34 | I2C_SMBUS = 0x0720 # SMBus transfer. Takes pointer to i2c_smbus_ioctl_data 35 | I2C_PEC = 0x0708 # != 0 to use PEC with SMBus 36 | 37 | # SMBus transfer read or write markers from uapi/linux/i2c.h 38 | I2C_SMBUS_WRITE = 0 39 | I2C_SMBUS_READ = 1 40 | 41 | # Size identifiers uapi/linux/i2c.h 42 | I2C_SMBUS_QUICK = 0 43 | I2C_SMBUS_BYTE = 1 44 | I2C_SMBUS_BYTE_DATA = 2 45 | I2C_SMBUS_WORD_DATA = 3 46 | I2C_SMBUS_PROC_CALL = 4 47 | I2C_SMBUS_BLOCK_DATA = 5 # This isn't supported by Pure-I2C drivers with SMBUS emulation, like those in RaspberryPi, OrangePi, etc :( 48 | I2C_SMBUS_BLOCK_PROC_CALL = 7 # Like I2C_SMBUS_BLOCK_DATA, it isn't supported by Pure-I2C drivers either. 49 | I2C_SMBUS_I2C_BLOCK_DATA = 8 50 | I2C_SMBUS_BLOCK_MAX = 32 51 | 52 | # To determine what functionality is present (uapi/linux/i2c.h) 53 | try: 54 | from enum import IntFlag 55 | except ImportError: 56 | IntFlag = int 57 | 58 | 59 | class I2cFunc(IntFlag): 60 | """ 61 | These flags identify the operations supported by an I2C/SMBus device. 62 | 63 | You can test these flags on your `smbus.funcs` 64 | 65 | On newer python versions, I2cFunc is an IntFlag enum, but it 66 | falls back to class with a bunch of int constants on older releases. 67 | """ 68 | I2C = 0x00000001 69 | ADDR_10BIT = 0x00000002 70 | PROTOCOL_MANGLING = 0x00000004 # I2C_M_IGNORE_NAK etc. 71 | SMBUS_PEC = 0x00000008 72 | NOSTART = 0x00000010 # I2C_M_NOSTART 73 | SLAVE = 0x00000020 74 | SMBUS_BLOCK_PROC_CALL = 0x00008000 # SMBus 2.0 75 | SMBUS_QUICK = 0x00010000 76 | SMBUS_READ_BYTE = 0x00020000 77 | SMBUS_WRITE_BYTE = 0x00040000 78 | SMBUS_READ_BYTE_DATA = 0x00080000 79 | SMBUS_WRITE_BYTE_DATA = 0x00100000 80 | SMBUS_READ_WORD_DATA = 0x00200000 81 | SMBUS_WRITE_WORD_DATA = 0x00400000 82 | SMBUS_PROC_CALL = 0x00800000 83 | SMBUS_READ_BLOCK_DATA = 0x01000000 84 | SMBUS_WRITE_BLOCK_DATA = 0x02000000 85 | SMBUS_READ_I2C_BLOCK = 0x04000000 # I2C-like block xfer 86 | SMBUS_WRITE_I2C_BLOCK = 0x08000000 # w/ 1-byte reg. addr. 87 | SMBUS_HOST_NOTIFY = 0x10000000 88 | 89 | SMBUS_BYTE = 0x00060000 90 | SMBUS_BYTE_DATA = 0x00180000 91 | SMBUS_WORD_DATA = 0x00600000 92 | SMBUS_BLOCK_DATA = 0x03000000 93 | SMBUS_I2C_BLOCK = 0x0c000000 94 | SMBUS_EMUL = 0x0eff0008 95 | 96 | 97 | # i2c_msg flags from uapi/linux/i2c.h 98 | I2C_M_RD = 0x0001 99 | 100 | # Pointer definitions 101 | LP_c_uint8 = POINTER(c_uint8) 102 | LP_c_uint16 = POINTER(c_uint16) 103 | LP_c_uint32 = POINTER(c_uint32) 104 | 105 | 106 | ############################################################# 107 | # Type definitions as in i2c.h 108 | 109 | 110 | class i2c_smbus_data(Array): 111 | """ 112 | Adaptation of the i2c_smbus_data union in ``i2c.h``. 113 | 114 | Data for SMBus messages. 115 | """ 116 | _length_ = I2C_SMBUS_BLOCK_MAX + 2 117 | _type_ = c_uint8 118 | 119 | 120 | class union_i2c_smbus_data(Union): 121 | _fields_ = [ 122 | ("byte", c_uint8), 123 | ("word", c_uint16), 124 | ("block", i2c_smbus_data) 125 | ] 126 | 127 | 128 | union_pointer_type = POINTER(union_i2c_smbus_data) 129 | 130 | 131 | class i2c_smbus_ioctl_data(Structure): 132 | """ 133 | As defined in ``i2c-dev.h``. 134 | """ 135 | _fields_ = [ 136 | ('read_write', c_uint8), 137 | ('command', c_uint8), 138 | ('size', c_uint32), 139 | ('data', union_pointer_type)] 140 | __slots__ = [name for name, type in _fields_] 141 | 142 | @staticmethod 143 | def create(read_write=I2C_SMBUS_READ, command=0, size=I2C_SMBUS_BYTE_DATA): 144 | u = union_i2c_smbus_data() 145 | return i2c_smbus_ioctl_data( 146 | read_write=read_write, command=command, size=size, 147 | data=union_pointer_type(u)) 148 | 149 | 150 | ############################################################# 151 | # Type definitions for i2c_rdwr combined transactions 152 | 153 | 154 | class i2c_msg(Structure): 155 | """ 156 | Represents a single I2C message for read or write operations. 157 | This is the expected data container for :py:meth:`SMBus.i2c_rdwr`. 158 | 159 | As defined in ``i2c.h``. 160 | """ 161 | _fields_ = [ 162 | ('addr', c_uint16), 163 | ('flags', c_uint16), 164 | ('len', c_uint16), 165 | ('buf', POINTER(c_char))] 166 | 167 | def __iter__(self): 168 | """ Iterator / Generator 169 | 170 | :return: iterates over :py:attr:`buf` 171 | :rtype: :py:class:`generator` which returns int values 172 | """ 173 | idx = 0 174 | while idx < self.len: 175 | yield ord(self.buf[idx]) 176 | idx += 1 177 | 178 | def __len__(self): 179 | return self.len 180 | 181 | def __bytes__(self): 182 | return string_at(self.buf, self.len) 183 | 184 | def __repr__(self): 185 | return 'i2c_msg(%d,%d,%r)' % (self.addr, self.flags, self.__bytes__()) 186 | 187 | def __str__(self): 188 | s = self.__bytes__() 189 | # Throw away non-decodable bytes 190 | s = s.decode(errors="ignore") 191 | return s 192 | 193 | @staticmethod 194 | def read(address, length): 195 | """ 196 | Prepares an i2c read transaction. 197 | 198 | :param address: Slave address. 199 | :type address: int 200 | :param length: Number of bytes to read. 201 | :type length: int 202 | :return: New :py:class:`i2c_msg` instance for read operation. 203 | :rtype: :py:class:`i2c_msg` 204 | """ 205 | arr = create_string_buffer(length) 206 | return i2c_msg( 207 | addr=address, flags=I2C_M_RD, len=length, 208 | buf=arr) 209 | 210 | @staticmethod 211 | def write(address, buf): 212 | """ 213 | Prepares an i2c write transaction. 214 | 215 | :param address: Slave address. 216 | :type address: int 217 | :param buf: Bytes to write. Either list of values or str. 218 | :type buf: bytes or str or list 219 | :return: New :py:class:`i2c_msg` instance for write operation. 220 | :rtype: :py:class:`i2c_msg` 221 | """ 222 | if sys.version_info.major >= 3: 223 | if type(buf) is str: 224 | buf = bytes(map(ord, buf)) 225 | else: 226 | buf = bytes(buf) 227 | else: 228 | if type(buf) is not str: 229 | buf = ''.join([chr(x) for x in buf]) 230 | arr = create_string_buffer(buf, len(buf)) 231 | return i2c_msg( 232 | addr=address, flags=0, len=len(arr), 233 | buf=arr) 234 | 235 | 236 | class i2c_rdwr_ioctl_data(Structure): 237 | """ 238 | As defined in ``i2c-dev.h``. 239 | """ 240 | _fields_ = [ 241 | ('msgs', POINTER(i2c_msg)), 242 | ('nmsgs', c_uint32) 243 | ] 244 | __slots__ = [name for name, type in _fields_] 245 | 246 | @staticmethod 247 | def create(*i2c_msg_instances): 248 | """ 249 | Factory method for creating a i2c_rdwr_ioctl_data struct that can 250 | be called with ``ioctl(fd, I2C_RDWR, data)``. 251 | 252 | :param i2c_msg_instances: Up to 42 i2c_msg instances 253 | :rtype: :py:class:`i2c_rdwr_ioctl_data` 254 | """ 255 | n_msg = len(i2c_msg_instances) 256 | msg_array = (i2c_msg * n_msg)(*i2c_msg_instances) 257 | return i2c_rdwr_ioctl_data( 258 | msgs=msg_array, 259 | nmsgs=n_msg 260 | ) 261 | 262 | 263 | ############################################################# 264 | 265 | 266 | class SMBus(object): 267 | """ 268 | Main class for I2C and SMBus communication, providing all IO functions for device access. 269 | """ 270 | 271 | def __init__(self, bus=None, force=False): 272 | """ 273 | Initialize and (optionally) open an i2c bus connection. 274 | 275 | :param bus: i2c bus number (e.g. 0 or 1) 276 | or an absolute file path (e.g. `/dev/i2c-42`). 277 | If not given, a subsequent call to ``open()`` is required. 278 | :type bus: int or str 279 | :param force: Use slave address even when driver is already using it. 280 | :type force: bool 281 | """ 282 | self.fd = None 283 | self.funcs = I2cFunc(0) 284 | if bus is not None: 285 | self.open(bus) 286 | self.address = None 287 | self.force = force 288 | self._force_last = None 289 | self._pec = 0 290 | 291 | def __enter__(self): 292 | """Enter handler.""" 293 | return self 294 | 295 | def __exit__(self, exc_type, exc_val, exc_tb): 296 | """Exit handler.""" 297 | self.close() 298 | 299 | def open(self, bus): 300 | """ 301 | Open a given i2c bus. 302 | 303 | :param bus: i2c bus number (e.g. 0 or 1) 304 | or an absolute file path (e.g. '/dev/i2c-42'). 305 | :type bus: int or str 306 | :raise TypeError: if type(bus) is not in (int, str) 307 | """ 308 | if isinstance(bus, int): 309 | filepath = "/dev/i2c-{}".format(bus) 310 | elif isinstance(bus, str): 311 | filepath = bus 312 | else: 313 | raise TypeError("Unexpected type(bus)={}".format(type(bus))) 314 | 315 | self.fd = os.open(filepath, os.O_RDWR) 316 | self.funcs = self._get_funcs() 317 | 318 | def close(self): 319 | """ 320 | Close the i2c connection. 321 | """ 322 | if self.fd: 323 | os.close(self.fd) 324 | self.fd = None 325 | self._pec = 0 326 | self.address = None 327 | self._force_last = None 328 | 329 | def _get_pec(self): 330 | return self._pec 331 | 332 | def enable_pec(self, enable=True): 333 | """ 334 | Enable/Disable PEC (Packet Error Checking) - SMBus 1.1 and later 335 | 336 | :param enable: 337 | :type enable: bool 338 | """ 339 | if not (self.funcs & I2cFunc.SMBUS_PEC): 340 | raise IOError('SMBUS_PEC is not a feature') 341 | self._pec = int(enable) 342 | ioctl(self.fd, I2C_PEC, self._pec) 343 | 344 | pec = property(_get_pec, enable_pec) # Drop-in replacement for smbus member "pec" 345 | """Get and set SMBus PEC. 0 = disabled (default), 1 = enabled.""" 346 | 347 | def _set_address(self, address, force=None): 348 | """ 349 | Set i2c slave address to use for subsequent calls. 350 | 351 | :param address: 352 | :type address: int 353 | :param force: Use slave address even when driver is already using it. 354 | :type force: bool 355 | """ 356 | force = force if force is not None else self.force 357 | if self.address != address or self._force_last != force: 358 | if force is True: 359 | ioctl(self.fd, I2C_SLAVE_FORCE, address) 360 | else: 361 | ioctl(self.fd, I2C_SLAVE, address) 362 | self.address = address 363 | self._force_last = force 364 | 365 | def _get_funcs(self): 366 | """ 367 | Returns a 32-bit value stating supported I2C functions. 368 | 369 | :rtype: int 370 | """ 371 | f = c_uint32() 372 | ioctl(self.fd, I2C_FUNCS, f) 373 | return f.value 374 | 375 | def write_quick(self, i2c_addr, force=None): 376 | """ 377 | Perform quick transaction. Throws IOError if unsuccessful. 378 | :param i2c_addr: i2c address 379 | :type i2c_addr: int 380 | :param force: Use slave address even when driver is already using it. 381 | :type force: bool 382 | """ 383 | self._set_address(i2c_addr, force=force) 384 | msg = i2c_smbus_ioctl_data.create( 385 | read_write=I2C_SMBUS_WRITE, command=0, size=I2C_SMBUS_QUICK) 386 | ioctl(self.fd, I2C_SMBUS, msg) 387 | 388 | def read_byte(self, i2c_addr, force=None): 389 | """ 390 | Read a single byte from a device. 391 | 392 | :rtype: int 393 | :param i2c_addr: i2c address 394 | :type i2c_addr: int 395 | :param force: Use slave address even when driver is already using it. 396 | :type force: bool 397 | :return: Read byte value 398 | """ 399 | self._set_address(i2c_addr, force=force) 400 | msg = i2c_smbus_ioctl_data.create( 401 | read_write=I2C_SMBUS_READ, command=0, size=I2C_SMBUS_BYTE 402 | ) 403 | ioctl(self.fd, I2C_SMBUS, msg) 404 | return msg.data.contents.byte 405 | 406 | def write_byte(self, i2c_addr, value, force=None): 407 | """ 408 | Write a single byte to a device. 409 | 410 | :param i2c_addr: i2c address 411 | :type i2c_addr: int 412 | :param value: value to write 413 | :type value: int 414 | :param force: Use slave address even when driver is already using it. 415 | :type force: bool 416 | """ 417 | self._set_address(i2c_addr, force=force) 418 | msg = i2c_smbus_ioctl_data.create( 419 | read_write=I2C_SMBUS_WRITE, command=value, size=I2C_SMBUS_BYTE 420 | ) 421 | ioctl(self.fd, I2C_SMBUS, msg) 422 | 423 | def read_byte_data(self, i2c_addr, register, force=None): 424 | """ 425 | Read a single byte from a designated register. 426 | 427 | :param i2c_addr: i2c address 428 | :type i2c_addr: int 429 | :param register: Register to read 430 | :type register: int 431 | :param force: Use slave address even when driver is already using it. 432 | :type force: bool 433 | :return: Read byte value 434 | :rtype: int 435 | """ 436 | self._set_address(i2c_addr, force=force) 437 | msg = i2c_smbus_ioctl_data.create( 438 | read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_BYTE_DATA 439 | ) 440 | ioctl(self.fd, I2C_SMBUS, msg) 441 | return msg.data.contents.byte 442 | 443 | def write_byte_data(self, i2c_addr, register, value, force=None): 444 | """ 445 | Write a byte to a given register. 446 | 447 | :param i2c_addr: i2c address 448 | :type i2c_addr: int 449 | :param register: Register to write to 450 | :type register: int 451 | :param value: Byte value to transmit 452 | :type value: int 453 | :param force: Use slave address even when driver is already using it. 454 | :type force: bool 455 | :rtype: None 456 | """ 457 | self._set_address(i2c_addr, force=force) 458 | msg = i2c_smbus_ioctl_data.create( 459 | read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_BYTE_DATA 460 | ) 461 | msg.data.contents.byte = value 462 | ioctl(self.fd, I2C_SMBUS, msg) 463 | 464 | def read_word_data(self, i2c_addr, register, force=None): 465 | """ 466 | Read a single word (2 bytes) from a given register. 467 | 468 | :param i2c_addr: i2c address 469 | :type i2c_addr: int 470 | :param register: Register to read 471 | :type register: int 472 | :param force: Use slave address even when driver is already using it. 473 | :type force: bool 474 | :return: 2-byte word 475 | :rtype: int 476 | """ 477 | self._set_address(i2c_addr, force=force) 478 | msg = i2c_smbus_ioctl_data.create( 479 | read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_WORD_DATA 480 | ) 481 | ioctl(self.fd, I2C_SMBUS, msg) 482 | return msg.data.contents.word 483 | 484 | def write_word_data(self, i2c_addr, register, value, force=None): 485 | """ 486 | Write a single word (2 bytes) to a given register. 487 | 488 | :param i2c_addr: i2c address 489 | :type i2c_addr: int 490 | :param register: Register to write to 491 | :type register: int 492 | :param value: Word value to transmit 493 | :type value: int 494 | :param force: Use slave address even when driver is already using it. 495 | :type force: bool 496 | :rtype: None 497 | """ 498 | self._set_address(i2c_addr, force=force) 499 | msg = i2c_smbus_ioctl_data.create( 500 | read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_WORD_DATA 501 | ) 502 | msg.data.contents.word = value 503 | ioctl(self.fd, I2C_SMBUS, msg) 504 | 505 | def process_call(self, i2c_addr, register, value, force=None): 506 | """ 507 | Executes a SMBus Process Call, sending a 16-bit value and receiving a 16-bit response 508 | 509 | :param i2c_addr: i2c address 510 | :type i2c_addr: int 511 | :param register: Register to read/write to 512 | :type register: int 513 | :param value: Word value to transmit 514 | :type value: int 515 | :param force: Use slave address even when driver is already using it. 516 | :type force: bool 517 | :rtype: int 518 | """ 519 | self._set_address(i2c_addr, force=force) 520 | msg = i2c_smbus_ioctl_data.create( 521 | read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_PROC_CALL 522 | ) 523 | msg.data.contents.word = value 524 | ioctl(self.fd, I2C_SMBUS, msg) 525 | return msg.data.contents.word 526 | 527 | def read_block_data(self, i2c_addr, register, force=None): 528 | """ 529 | Read a block of up to 32-bytes from a given register. 530 | 531 | :param i2c_addr: i2c address 532 | :type i2c_addr: int 533 | :param register: Start register 534 | :type register: int 535 | :param force: Use slave address even when driver is already using it. 536 | :type force: bool 537 | :return: List of bytes 538 | :rtype: list 539 | """ 540 | self._set_address(i2c_addr, force=force) 541 | msg = i2c_smbus_ioctl_data.create( 542 | read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_BLOCK_DATA 543 | ) 544 | ioctl(self.fd, I2C_SMBUS, msg) 545 | length = msg.data.contents.block[0] 546 | return msg.data.contents.block[1:length + 1] 547 | 548 | def write_block_data(self, i2c_addr, register, data, force=None): 549 | """ 550 | Write a block of byte data to a given register. 551 | 552 | :param i2c_addr: i2c address 553 | :type i2c_addr: int 554 | :param register: Start register 555 | :type register: int 556 | :param data: List of bytes 557 | :type data: list 558 | :param force: Use slave address even when driver is already using it. 559 | :type force: bool 560 | :rtype: None 561 | """ 562 | length = len(data) 563 | if length > I2C_SMBUS_BLOCK_MAX: 564 | raise ValueError("Data length cannot exceed %d bytes" % I2C_SMBUS_BLOCK_MAX) 565 | self._set_address(i2c_addr, force=force) 566 | msg = i2c_smbus_ioctl_data.create( 567 | read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_BLOCK_DATA 568 | ) 569 | msg.data.contents.block[0] = length 570 | msg.data.contents.block[1:length + 1] = data 571 | ioctl(self.fd, I2C_SMBUS, msg) 572 | 573 | def block_process_call(self, i2c_addr, register, data, force=None): 574 | """ 575 | Executes a SMBus Block Process Call, sending a variable-size data 576 | block and receiving another variable-size response 577 | 578 | :param i2c_addr: i2c address 579 | :type i2c_addr: int 580 | :param register: Register to read/write to 581 | :type register: int 582 | :param data: List of bytes 583 | :type data: list 584 | :param force: Use slave address even when driver is already using it. 585 | :type force: bool 586 | :return: List of bytes 587 | :rtype: list 588 | """ 589 | length = len(data) 590 | if length > I2C_SMBUS_BLOCK_MAX: 591 | raise ValueError("Data length cannot exceed %d bytes" % I2C_SMBUS_BLOCK_MAX) 592 | self._set_address(i2c_addr, force=force) 593 | msg = i2c_smbus_ioctl_data.create( 594 | read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_BLOCK_PROC_CALL 595 | ) 596 | msg.data.contents.block[0] = length 597 | msg.data.contents.block[1:length + 1] = data 598 | ioctl(self.fd, I2C_SMBUS, msg) 599 | length = msg.data.contents.block[0] 600 | return msg.data.contents.block[1:length + 1] 601 | 602 | def read_i2c_block_data(self, i2c_addr, register, length, force=None): 603 | """ 604 | Read a block of byte data from a given register. 605 | 606 | :param i2c_addr: i2c address 607 | :type i2c_addr: int 608 | :param register: Start register 609 | :type register: int 610 | :param length: Desired block length 611 | :type length: int 612 | :param force: Use slave address even when driver is already using it. 613 | :type force: bool 614 | :return: List of bytes 615 | :rtype: list 616 | """ 617 | if length > I2C_SMBUS_BLOCK_MAX: 618 | raise ValueError("Desired block length over %d bytes" % I2C_SMBUS_BLOCK_MAX) 619 | self._set_address(i2c_addr, force=force) 620 | msg = i2c_smbus_ioctl_data.create( 621 | read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_I2C_BLOCK_DATA 622 | ) 623 | msg.data.contents.byte = length 624 | ioctl(self.fd, I2C_SMBUS, msg) 625 | return msg.data.contents.block[1:length + 1] 626 | 627 | def write_i2c_block_data(self, i2c_addr, register, data, force=None): 628 | """ 629 | Write a block of byte data to a given register. 630 | 631 | :param i2c_addr: i2c address 632 | :type i2c_addr: int 633 | :param register: Start register 634 | :type register: int 635 | :param data: List of bytes 636 | :type data: list 637 | :param force: Use slave address even when driver is already using it. 638 | :type force: bool 639 | :rtype: None 640 | """ 641 | length = len(data) 642 | if length > I2C_SMBUS_BLOCK_MAX: 643 | raise ValueError("Data length cannot exceed %d bytes" % I2C_SMBUS_BLOCK_MAX) 644 | self._set_address(i2c_addr, force=force) 645 | msg = i2c_smbus_ioctl_data.create( 646 | read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_I2C_BLOCK_DATA 647 | ) 648 | msg.data.contents.block[0] = length 649 | msg.data.contents.block[1:length + 1] = data 650 | ioctl(self.fd, I2C_SMBUS, msg) 651 | 652 | def i2c_rdwr(self, *i2c_msgs): 653 | """ 654 | Combine a series of i2c read and write operations in a single 655 | transaction (with repeated start bits but no stop bits in between). 656 | 657 | This method takes i2c_msg instances as input, which must be created 658 | first with :py:meth:`i2c_msg.read` or :py:meth:`i2c_msg.write`. 659 | 660 | :param i2c_msgs: One or more i2c_msg class instances. 661 | :type i2c_msgs: i2c_msg 662 | :rtype: None 663 | """ 664 | ioctl_data = i2c_rdwr_ioctl_data.create(*i2c_msgs) 665 | ioctl(self.fd, I2C_RDWR, ioctl_data) 666 | -------------------------------------------------------------------------------- /smbus2/smbus2.pyi: -------------------------------------------------------------------------------- 1 | from enum import IntFlag 2 | from typing import Optional, Sequence, List, Type, SupportsBytes, Iterable 3 | from typing import Union as _UnionT 4 | from types import TracebackType 5 | from ctypes import c_uint32, c_uint8, c_uint16, pointer, Structure, Array, Union 6 | 7 | I2C_SLAVE: int 8 | I2C_SLAVE_FORCE: int 9 | I2C_FUNCS: int 10 | I2C_RDWR: int 11 | I2C_SMBUS: int 12 | I2C_PEC: int 13 | I2C_SMBUS_WRITE: int 14 | I2C_SMBUS_READ: int 15 | I2C_SMBUS_QUICK: int 16 | I2C_SMBUS_BYTE: int 17 | I2C_SMBUS_BYTE_DATA: int 18 | I2C_SMBUS_WORD_DATA: int 19 | I2C_SMBUS_PROC_CALL: int 20 | I2C_SMBUS_BLOCK_DATA: int 21 | I2C_SMBUS_BLOCK_PROC_CALL: int 22 | I2C_SMBUS_I2C_BLOCK_DATA: int 23 | I2C_SMBUS_BLOCK_MAX: int 24 | 25 | class I2cFunc(IntFlag): 26 | I2C = ... 27 | ADDR_10BIT = ... 28 | PROTOCOL_MANGLING = ... 29 | SMBUS_PEC = ... 30 | NOSTART = ... 31 | SLAVE = ... 32 | SMBUS_BLOCK_PROC_CALL = ... 33 | SMBUS_QUICK = ... 34 | SMBUS_READ_BYTE = ... 35 | SMBUS_WRITE_BYTE = ... 36 | SMBUS_READ_BYTE_DATA = ... 37 | SMBUS_WRITE_BYTE_DATA = ... 38 | SMBUS_READ_WORD_DATA = ... 39 | SMBUS_WRITE_WORD_DATA = ... 40 | SMBUS_PROC_CALL = ... 41 | SMBUS_READ_BLOCK_DATA = ... 42 | SMBUS_WRITE_BLOCK_DATA = ... 43 | SMBUS_READ_I2C_BLOCK = ... 44 | SMBUS_WRITE_I2C_BLOCK = ... 45 | SMBUS_HOST_NOTIFY = ... 46 | SMBUS_BYTE = ... 47 | SMBUS_BYTE_DATA = ... 48 | SMBUS_WORD_DATA = ... 49 | SMBUS_BLOCK_DATA = ... 50 | SMBUS_I2C_BLOCK = ... 51 | SMBUS_EMUL = ... 52 | 53 | I2C_M_RD: int 54 | LP_c_uint8: Type[pointer[c_uint8]] 55 | LP_c_uint16: Type[pointer[c_uint16]] 56 | LP_c_uint32: Type[pointer[c_uint32]] 57 | 58 | class i2c_smbus_data(Array): ... 59 | class union_i2c_smbus_data(Union): ... 60 | 61 | union_pointer_type: pointer[union_i2c_smbus_data] 62 | 63 | class i2c_smbus_ioctl_data(Structure): 64 | @staticmethod 65 | def create( 66 | read_write: int = ..., command: int = ..., size: int = ... 67 | ) -> "i2c_smbus_ioctl_data": ... 68 | 69 | class i2c_msg(Structure): 70 | def __iter__(self) -> int: ... 71 | def __len__(self) -> int: ... 72 | def __bytes__(self) -> str: ... 73 | @staticmethod 74 | def read(address: int, length: int) -> "i2c_msg": ... 75 | @staticmethod 76 | def write(address: int, buf: _UnionT[str, Iterable[int], SupportsBytes]) -> "i2c_msg": ... 77 | 78 | class i2c_rdwr_ioctl_data(Structure): 79 | @staticmethod 80 | def create(*i2c_msg_instances: Sequence[i2c_msg]) -> "i2c_rdwr_ioctl_data": ... 81 | 82 | class SMBus: 83 | fd: Optional[int] = ... 84 | funcs: I2cFunc = ... 85 | address: Optional[int] = ... 86 | force: bool = ... 87 | pec: int = ... 88 | def __init__( 89 | self, bus: _UnionT[None, int, str] = ..., force: bool = ... 90 | ) -> None: ... 91 | def __enter__(self) -> "SMBus": ... 92 | def __exit__( 93 | self, 94 | exc_type: Optional[Type[BaseException]], 95 | exc_val: Optional[BaseException], 96 | exc_tb: Optional[TracebackType], 97 | ) -> None: ... 98 | def open(self, bus: _UnionT[int, str]) -> None: ... 99 | def close(self) -> None: ... 100 | def enable_pec(self, enable: bool = ...) -> None: ... 101 | def write_quick(self, i2c_addr: int, force: Optional[bool] = ...) -> None: ... 102 | def read_byte(self, i2c_addr: int, force: Optional[bool] = ...) -> int: ... 103 | def write_byte( 104 | self, i2c_addr: int, value: int, force: Optional[bool] = ... 105 | ) -> None: ... 106 | def read_byte_data( 107 | self, i2c_addr: int, register: int, force: Optional[bool] = ... 108 | ) -> int: ... 109 | def write_byte_data( 110 | self, i2c_addr: int, register: int, value: int, force: Optional[bool] = ... 111 | ) -> None: ... 112 | def read_word_data( 113 | self, i2c_addr: int, register: int, force: Optional[bool] = ... 114 | ) -> int: ... 115 | def write_word_data( 116 | self, i2c_addr: int, register: int, value: int, force: Optional[bool] = ... 117 | ) -> None: ... 118 | def process_call( 119 | self, i2c_addr: int, register: int, value: int, force: Optional[bool] = ... 120 | ): ... 121 | def read_block_data( 122 | self, i2c_addr: int, register: int, force: Optional[bool] = ... 123 | ) -> List[int]: ... 124 | def write_block_data( 125 | self, 126 | i2c_addr: int, 127 | register: int, 128 | data: Sequence[int], 129 | force: Optional[bool] = ..., 130 | ) -> None: ... 131 | def block_process_call( 132 | self, 133 | i2c_addr: int, 134 | register: int, 135 | data: Sequence[int], 136 | force: Optional[bool] = ..., 137 | ) -> List[int]: ... 138 | def read_i2c_block_data( 139 | self, i2c_addr: int, register: int, length: int, force: Optional[bool] = ... 140 | ) -> List[int]: ... 141 | def write_i2c_block_data( 142 | self, 143 | i2c_addr: int, 144 | register: int, 145 | data: Sequence[int], 146 | force: Optional[bool] = ..., 147 | ) -> None: ... 148 | def i2c_rdwr(self, *i2c_msgs: i2c_msg) -> None: ... 149 | -------------------------------------------------------------------------------- /tests/test_datatypes.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2020 Karl-Petter Lindegaard 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | 22 | from smbus2.smbus2 import i2c_smbus_ioctl_data, union_i2c_smbus_data, i2c_msg, i2c_rdwr_ioctl_data # noqa: F401 23 | from smbus2.smbus2 import I2C_SMBUS_BLOCK_MAX, I2C_SMBUS_READ, I2C_SMBUS_BYTE_DATA 24 | import unittest 25 | 26 | 27 | class TestDataTypes(unittest.TestCase): 28 | 29 | def test_union_i2c_smbus_data(self): 30 | u = union_i2c_smbus_data() 31 | 32 | # Fill array with values 1, 2, ... 33 | for k in range(I2C_SMBUS_BLOCK_MAX + 2): 34 | u.block[k] = k + 1 35 | 36 | # Check that the union works 37 | self.assertEqual(u.byte, u.block[0], msg="Byte field differ") 38 | self.assertEqual(u.block[16], 17, msg="Array field does not match") 39 | 40 | # Set byte and se it reflected in the array 41 | u.byte = 255 42 | self.assertEqual(u.block[0], 255, msg="Byte field not reflected in array") 43 | 44 | # Reset array to zeros and check word field 45 | for k in range(I2C_SMBUS_BLOCK_MAX + 2): 46 | u.block[k] = 0 47 | u.word = 1607 48 | self.assertNotEqual(0, u.word, msg="Word field is zero but should be non-zero") 49 | u.word = 0 50 | 51 | def test_i2c_smbus_ioctl_data_factory(self): 52 | ioctl_msg = i2c_smbus_ioctl_data.create() 53 | 54 | self.assertEqual(ioctl_msg.read_write, I2C_SMBUS_READ) 55 | self.assertEqual(ioctl_msg.size, I2C_SMBUS_BYTE_DATA) 56 | 57 | # Simple test to check assignment 58 | ioctl_msg.data.contents.byte = 25 59 | self.assertEqual(ioctl_msg.data.contents.byte, 25, msg="Get not equal to set") 60 | 61 | def test_i2c_msg_read(self): 62 | msg = i2c_msg.read(80, 10) 63 | self.assertEqual(msg.addr, 80) 64 | self.assertEqual(msg.len, 10) 65 | self.assertEqual(msg.len, len(msg)) 66 | self.assertEqual(msg.flags, 1) 67 | 68 | def test_i2c_msg_write(self): 69 | # Create from list 70 | buf = [65, 66, 67, 68, 1, 10, 255] 71 | msg = i2c_msg.write(81, buf) 72 | self.assertEqual(msg.addr, 81) 73 | self.assertEqual(msg.len, 7) 74 | self.assertEqual(msg.len, len(msg)) 75 | self.assertEqual(msg.flags, 0) 76 | self.assertListEqual(buf, list(msg)) 77 | # Create from str 78 | s = "ABCD\x01\n\xFF" 79 | msg2 = i2c_msg.write(81, s) 80 | self.assertEqual(msg2.addr, msg.addr) 81 | self.assertEqual(msg2.len, msg.len) 82 | self.assertEqual(msg2.flags, msg.flags) 83 | self.assertListEqual(list(msg), list(msg2)) 84 | self.assertEqual(str(msg2), "ABCD\x01\n") 85 | self.assertGreaterEqual(('%r' % msg2).find(r"ABCD\x01\n\xff"), 0) 86 | 87 | def test_i2c_msg_iter(self): 88 | buf = [10, 11, 12, 13] 89 | msg = i2c_msg.write(81, buf) 90 | # Convert msg to list and compare 91 | msg_list = list(msg) 92 | self.assertListEqual(buf, msg_list) 93 | # Loop over each message entry 94 | i = 0 95 | for value in msg: 96 | self.assertEqual(value, buf[i]) 97 | i += 1 98 | # Loop over with index and value 99 | for i, value in enumerate(msg): 100 | self.assertEqual(i + 10, value) 101 | -------------------------------------------------------------------------------- /tests/test_smbus2.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2020 Karl-Petter Lindegaard 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | 22 | import sys 23 | import unittest 24 | 25 | try: 26 | import unittest.mock as mock 27 | except ImportError: 28 | import mock # noqa: F401 29 | 30 | from smbus2 import SMBus, i2c_msg, I2cFunc 31 | 32 | 33 | ########################################################################## 34 | # Mock open, close and ioctl so we can run our unit tests anywhere. 35 | 36 | # Required I2C constant definitions repeated 37 | I2C_FUNCS = 0x0705 # Get the adapter functionality mask 38 | I2C_SMBUS = 0x0720 39 | I2C_SMBUS_WRITE = 0 40 | I2C_SMBUS_READ = 1 41 | 42 | I2C_SMBUS_QUICK = 0 43 | I2C_SMBUS_BYTE_DATA = 2 44 | I2C_SMBUS_WORD_DATA = 3 45 | I2C_SMBUS_BLOCK_DATA = 5 # Can't get this one to work on my Raspberry Pi 46 | I2C_SMBUS_I2C_BLOCK_DATA = 8 47 | I2C_SMBUS_BLOCK_MAX = 32 48 | 49 | MOCK_FD = "Mock file descriptor" 50 | 51 | # Test buffer for read operations 52 | test_buffer = [x for x in range(256)] 53 | 54 | 55 | def bytes_six(lst): 56 | """convert a list of int to `bytes` like object""" 57 | if sys.version_info.major >= 3: 58 | return bytes(lst) 59 | else: 60 | return ''.join(map(chr, lst)) 61 | 62 | 63 | def mock_open(*args): 64 | print("Mocking open: %s" % args[0]) 65 | return MOCK_FD 66 | 67 | 68 | def mock_close(*args): 69 | assert args[0] == MOCK_FD 70 | 71 | 72 | def mock_read(fd, length): 73 | assert fd == MOCK_FD 74 | return bytes_six(test_buffer[0:length]) 75 | 76 | 77 | def mock_ioctl(fd, command, msg): 78 | print("Mocking ioctl") 79 | assert fd == MOCK_FD 80 | assert command is not None 81 | 82 | # Reproduce i2c capability of a Raspberry Pi 3 w/o PEC support 83 | if command == I2C_FUNCS: 84 | msg.value = 0xeff0001 85 | return 86 | 87 | # Reproduce ioctl read operations 88 | if command == I2C_SMBUS and msg.read_write == I2C_SMBUS_READ: 89 | offset = msg.command 90 | if msg.size == I2C_SMBUS_BYTE_DATA: 91 | msg.data.contents.byte = test_buffer[offset] 92 | elif msg.size == I2C_SMBUS_WORD_DATA: 93 | msg.data.contents.word = test_buffer[offset + 1] * 256 + test_buffer[offset] 94 | elif msg.size == I2C_SMBUS_I2C_BLOCK_DATA: 95 | for k in range(msg.data.contents.byte): 96 | msg.data.contents.block[k + 1] = test_buffer[offset + k] 97 | 98 | # Reproduce a failing Quick write transaction 99 | if command == I2C_SMBUS and \ 100 | msg.read_write == I2C_SMBUS_WRITE and \ 101 | msg.size == I2C_SMBUS_QUICK: 102 | raise IOError("Mocking SMBus Quick failed") 103 | 104 | 105 | # Override open, close and ioctl with our mock functions 106 | open_mock = mock.patch('smbus2.smbus2.os.open', mock_open) 107 | close_mock = mock.patch('smbus2.smbus2.os.close', mock_close) 108 | ioctl_mock = mock.patch('smbus2.smbus2.ioctl', mock_ioctl) 109 | ########################################################################## 110 | 111 | # Common error messages 112 | INCORRECT_LENGTH_MSG = "Result array of incorrect length." 113 | 114 | 115 | class SMBusTestCase(unittest.TestCase): 116 | def setUp(self): 117 | open_mock.start() 118 | close_mock.start() 119 | ioctl_mock.start() 120 | 121 | def tearDown(self): 122 | open_mock.stop() 123 | close_mock.stop() 124 | ioctl_mock.stop() 125 | 126 | 127 | # Test cases 128 | class TestSMBus(SMBusTestCase): 129 | def test_func(self): 130 | bus = SMBus(1) 131 | print("\nSupported I2C functionality: %x" % bus.funcs) 132 | bus.close() 133 | 134 | def test_enter_exit(self): 135 | for id in (1, '/dev/i2c-alias'): 136 | with SMBus(id) as bus: 137 | self.assertIsNotNone(bus.fd) 138 | self.assertIsNone(bus.fd, None) 139 | 140 | with SMBus() as bus: 141 | self.assertIsNone(bus.fd) 142 | bus.open(2) 143 | self.assertIsNotNone(bus.fd) 144 | self.assertIsNone(bus.fd) 145 | 146 | def test_open_close(self): 147 | for id in (1, '/dev/i2c-alias'): 148 | bus = SMBus() 149 | self.assertIsNone(bus.fd) 150 | bus.open(id) 151 | self.assertIsNotNone(bus.fd) 152 | bus.close() 153 | self.assertIsNone(bus.fd) 154 | 155 | def test_read(self): 156 | res = [] 157 | res2 = [] 158 | res3 = [] 159 | 160 | bus = SMBus(1) 161 | 162 | # Read bytes 163 | for k in range(2): 164 | x = bus.read_byte_data(80, k) 165 | res.append(x) 166 | self.assertEqual(len(res), 2, msg=INCORRECT_LENGTH_MSG) 167 | 168 | # Read word 169 | x = bus.read_word_data(80, 0) 170 | res2.append(x & 255) 171 | res2.append(x / 256) 172 | self.assertEqual(len(res2), 2, msg=INCORRECT_LENGTH_MSG) 173 | self.assertListEqual(res, res2, msg="Byte and word reads differ") 174 | 175 | # Read block of N bytes 176 | n = 2 177 | x = bus.read_i2c_block_data(80, 0, n) 178 | res3.extend(x) 179 | self.assertEqual(len(res3), n, msg=INCORRECT_LENGTH_MSG) 180 | self.assertListEqual(res, res3, msg="Byte and block reads differ") 181 | 182 | bus.close() 183 | 184 | def test_quick(self): 185 | bus = SMBus(1) 186 | self.assertRaises(IOError, bus.write_quick, 80) 187 | 188 | def test_pec(self): 189 | def set_pec(bus, enable=True): 190 | bus.pec = enable 191 | 192 | # Enabling PEC should fail (no mocked PEC support) 193 | bus = SMBus(1) 194 | self.assertRaises(IOError, set_pec, bus, True) 195 | self.assertRaises(IOError, set_pec, bus, 1) 196 | self.assertEqual(bus.pec, 0) 197 | 198 | # Ensure PEC status is reset by close() 199 | bus._pec = 1 200 | self.assertEqual(bus.pec, 1) 201 | bus.close() 202 | self.assertEqual(bus.pec, 0) 203 | 204 | 205 | class TestSMBusWrapper(SMBusTestCase): 206 | """Similar test as TestSMBus except it encapsulates it all access within "with" blocks.""" 207 | 208 | def test_func(self): 209 | with SMBus(1) as bus: 210 | print("\nSupported I2C functionality: 0x%X" % bus.funcs) 211 | self.assertTrue(bus.funcs & I2cFunc.I2C > 0) 212 | self.assertTrue(bus.funcs & I2cFunc.SMBUS_QUICK > 0) 213 | 214 | def test_read(self): 215 | res = [] 216 | res2 = [] 217 | res3 = [] 218 | 219 | # Read bytes 220 | with SMBus(1) as bus: 221 | for k in range(2): 222 | x = bus.read_byte_data(80, k) 223 | res.append(x) 224 | self.assertEqual(len(res), 2, msg=INCORRECT_LENGTH_MSG) 225 | 226 | # Read word 227 | with SMBus(1) as bus: 228 | x = bus.read_word_data(80, 0) 229 | res2.append(x & 255) 230 | res2.append(x / 256) 231 | self.assertEqual(len(res2), 2, msg=INCORRECT_LENGTH_MSG) 232 | self.assertListEqual(res, res2, msg="Byte and word reads differ") 233 | 234 | # Read block of N bytes 235 | n = 2 236 | with SMBus(1) as bus: 237 | x = bus.read_i2c_block_data(80, 0, n) 238 | res3.extend(x) 239 | self.assertEqual(len(res3), n, msg=INCORRECT_LENGTH_MSG) 240 | self.assertListEqual(res, res3, msg="Byte and block reads differ") 241 | 242 | 243 | class TestI2CMsg(SMBusTestCase): 244 | def test_i2c_msg(self): 245 | # 1: Convert message content to list 246 | msg = i2c_msg.write(60, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 247 | data = list(msg) 248 | self.assertEqual(len(data), 10) 249 | 250 | # 2: i2c_msg is iterable 251 | k = 0 252 | s = 0 253 | for value in msg: 254 | k += 1 255 | s += value 256 | self.assertEqual(k, 10, msg='Incorrect length') 257 | self.assertEqual(s, 55, msg='Incorrect sum') 258 | 259 | # 3: Through i2c_msg properties 260 | k = 0 261 | s = 0 262 | for k in range(msg.len): 263 | s += ord(msg.buf[k]) 264 | k += 1 265 | self.assertEqual(k, 10, msg='Incorrect length') 266 | self.assertEqual(s, 55, msg='Incorrect sum') 267 | --------------------------------------------------------------------------------