├── tests ├── __init__.py ├── test_Crc8.py ├── test_Packer.py └── test_Unpacker.py ├── version.py ├── Raspberry_Pi_Master_for_ESP32_I2C_SLAVE ├── __init__.py ├── crc8.py ├── unpacker.py └── packer.py ├── .pypirc ├── setup.cfg ├── .gitignore ├── pyproject.toml ├── _config.yml ├── .github └── workflows │ ├── greetings.yml │ ├── stale.yml │ ├── label.yml │ ├── python-publish.yml │ └── python-package.yml ├── requirements.txt ├── example ├── raspberry_pi_write_esp32.py ├── raspberry_pi_read_esp32.py └── esp32_slave_side.cpp ├── README.md ├── tox.ini └── setup.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = "0.0.6" 3 | -------------------------------------------------------------------------------- /Raspberry_Pi_Master_for_ESP32_I2C_SLAVE/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.pypirc: -------------------------------------------------------------------------------- 1 | [distutils] 2 | index-servers= 3 | pypi 4 | 5 | [pypi] 6 | repository = https://pypi.python.org/pypi 7 | username = ${{ secrets.PYPI_USERNAME }} 8 | password = ${{ secrets.PYPI_PASSWORD }} -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | # This includes the license file(s) in the wheel. 3 | # https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file 4 | license_files = LICENSE.txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # general things to ignore 2 | build/ 3 | dist/ 4 | *.egg-info/ 5 | *.egg 6 | *.py[cod] 7 | __pycache__/ 8 | *.pyc 9 | *.so 10 | *~ 11 | venv/ 12 | .vscode/ 13 | .pytest_cache 14 | 15 | # due to using tox and pytest 16 | .tox 17 | .cache -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # These are the assumed default build requirements from pip: 3 | # https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support 4 | requires = ["setuptools>=49.2.0", "wheel"] 5 | build-backend = "setuptools.build_meta" 6 | 7 | #[tool.check-manifest] 8 | #ignore = ["venv/"] -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate 2 | 3 | gems: 4 | - jekyll-seo-tag 5 | - jekyll-last-modified-at 6 | 7 | plugins: 8 | - jekyll-seo-tag 9 | - jekyll-last-modified-at 10 | 11 | # Optional. The default date format, used if none is specified in the tag. 12 | last-modified-at: 13 | date-format: '%d-%b-%y' -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Message that will be displayed on users'' first issue' 13 | pr-message: 'Message that will be displayed on users'' first pr' 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # install from github 2 | # pip3 install -e git+https://github.com/GreenPonik/GreenPonik_WaterPump_Driver.git#egg=greenponik-waterpump-driver 3 | 4 | # upgrade from github 5 | # pip3 install -e git+https://github.com/GreenPonik/GreenPonik_WaterPump_Driver.git#egg=greenponik-waterpump-driver --upgrade 6 | 7 | 8 | adafruit-blinka>=5.2.3 9 | adafruit-extended-bus==1.0.0 10 | tox==3.18.0 11 | twine==3.2.0 12 | pytest==5.4.3 13 | flake8==3.8.3 14 | check-manifest>=0.42 -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v1 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'Stale issue message' 17 | stale-pr-message: 'Stale pull request message' 18 | stale-issue-label: 'no-issue-activity' 19 | stale-pr-label: 'no-pr-activity' 20 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler 7 | 8 | name: Labeler 9 | on: [pull_request] 10 | 11 | jobs: 12 | label: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/labeler@v2 18 | with: 19 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 20 | -------------------------------------------------------------------------------- /tests/test_Crc8.py: -------------------------------------------------------------------------------- 1 | # the inclusion of the tests module is not meant to offer best practices for 2 | # testing in general, but rather to support the `find_packages` example in 3 | # setup.py that excludes installing the "tests" package 4 | 5 | import sys 6 | import unittest 7 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.crc8 import Crc8 8 | 9 | 10 | class TestPacker(unittest.TestCase): 11 | def test_crc8(self): 12 | value_to_convert = [12, 1] 13 | _crc8 = None 14 | with Crc8() as crc8: 15 | _crc8 = crc8.calc(value_to_convert) 16 | expected = 19 17 | self.assertIsNotNone(_crc8) 18 | self.assertTrue(type(_crc8).__name__ == "int") 19 | self.assertEqual(expected, _crc8) 20 | 21 | 22 | if __name__ == "__main__": 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /tests/test_Packer.py: -------------------------------------------------------------------------------- 1 | # the inclusion of the tests module is not meant to offer best practices for 2 | # testing in general, but rather to support the `find_packages` example in 3 | # setup.py that excludes installing the "tests" package 4 | 5 | import sys 6 | import unittest 7 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.packer import Packer 8 | 9 | 10 | class TestPacker(unittest.TestCase): 11 | def test_packer(self): 12 | packed = None 13 | with Packer() as packer: 14 | packer.write(127) 15 | packer.end() 16 | packed = [i for i in packer.read() if i != 0] 17 | expected = [2, 5, 127, 185, 4] 18 | self.assertIsNotNone(packed) 19 | self.assertTrue(type(packed).__name__ == "list") 20 | self.assertEqual(expected, packed) 21 | 22 | 23 | if __name__ == "__main__": 24 | unittest.main() 25 | -------------------------------------------------------------------------------- /tests/test_Unpacker.py: -------------------------------------------------------------------------------- 1 | # the inclusion of the tests module is not meant to offer best practices for 2 | # testing in general, but rather to support the `find_packages` example in 3 | # setup.py that excludes installing the "tests" package 4 | 5 | import sys 6 | import unittest 7 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.unpacker import Unpacker 8 | 9 | 10 | class TestPacker(unittest.TestCase): 11 | def test_unpacker(self): 12 | value_to_unpack = [2, 6, 12, 1, 19, 4] 13 | unpacked = None 14 | with Unpacker() as unpacker: 15 | unpacker.write(value_to_unpack) 16 | unpacked = unpacker.read() 17 | expected = [12, 1] 18 | self.assertIsNotNone(unpacked) 19 | self.assertTrue(type(unpacked).__name__ == "list") 20 | self.assertEqual(expected, unpacked) 21 | 22 | 23 | if __name__ == "__main__": 24 | unittest.main() 25 | -------------------------------------------------------------------------------- /example/raspberry_pi_write_esp32.py: -------------------------------------------------------------------------------- 1 | from Adafruit_PureIO.smbus import SMBus # pip install adafruit-blinka 2 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.packer import Packer 3 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.unpacker import Unpacker 4 | import time 5 | 6 | 7 | slave_address = 0x04 # slave address is 4 8 | register = 0x01 # register to write is 0x01 9 | value = 0x04 10 | 11 | 12 | def write_from_rpi_to_esp32(): 13 | try: 14 | # change 1 of SMBus(1) to bus number on your RPI 15 | smbus = SMBus(1) 16 | # prepare the data 17 | packed = None 18 | with Packer() as packer: 19 | packer.write(register) 20 | packer.write(value) 21 | packer.end() 22 | packed = packer.read() 23 | smbus.write_bytes(register, bytearray(packed)) 24 | except Exception as e: 25 | print("ERROR: {}".format(e)) 26 | -------------------------------------------------------------------------------- /.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: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 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_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry_Pi_Master_for_ESP32_I2C_SLAVE 2 | # This Python lib only works with the v0.2.0 of the c++ library https://github.com/gutierrezps/ESP32_I2C_Slave/releases/tag/v0.2.0 3 | 4 | ## use __**Raspberry pi as MASTER**__ of a __**ESP32 SLAVE**__ on __**i2c bus**__ 5 | 6 | ### To use ESP32 as slave on i2c bus you have to use ESP32_I2C_Slave c++ library: 7 | > platformio lib_deps = ESP32 I2C Slave
8 | > https://github.com/gutierrezps/ESP32_I2C_Slave 9 | 10 | because the esp32-arduino framework not allowed you to use ESP32 as i2c slave. 11 | 12 | The ESP32 I2C Slave library do the job on 2 ESP32 or Arduino + ESP32 but not with python master on raspberry pi. 13 | 14 | So i convert parts of this library to python classes. 15 | 16 | To use these classes you must need to install: 17 | 18 | >pip install adafruit-blinka
19 | >pip install adafruit-extended-bus 20 | 21 | 22 | Follow examples to read data from master RPI to slave ESP32:
23 | [example-read](/example/raspberry_pi_read_esp32.py)
24 | [example-write](/example/raspberry_pi_write_esp32.py)
25 | [example-slave-side](/example/esp32_slave_side.cpp)
26 | 27 | 28 | to install it use pip: 29 | >pip install raspberrypi-esp32-i2c -------------------------------------------------------------------------------- /Raspberry_Pi_Master_for_ESP32_I2C_SLAVE/crc8.py: -------------------------------------------------------------------------------- 1 | """ 2 | @file Crc8.py 3 | @author Mickael Lehoux 4 | @brief build CRC8 in python 5 | @date 2020-09-18 6 | based on: 7 | https://github.com/gutierrezps/ESP32_I2C_Slave/blob/master/src/WireCrc.h 8 | """ 9 | 10 | 11 | class Crc8: 12 | def __init__(self): 13 | self._seed = 0 14 | self._debug = False 15 | 16 | def __enter__(self): 17 | """Context manager enter function.""" 18 | # Just return this object so it can be used in a with statement, like 19 | # with Crc8() as crc8: 20 | # # do stuff! 21 | return self 22 | 23 | def __exit__(self, exc_type, exc_val, exc_tb): 24 | """Context manager exit function, ensures resources are cleaned up.""" 25 | return False # Don't suppress exceptions. 26 | 27 | def calc(self, data): 28 | crc = self._seed 29 | for _byte in data: 30 | extract = _byte 31 | for j in range(8, 0, -1): 32 | _sum = (crc ^ extract) & 0x01 33 | crc >>= 1 34 | if _sum: 35 | crc ^= 0x8C 36 | extract >>= 1 37 | return crc 38 | -------------------------------------------------------------------------------- /example/raspberry_pi_read_esp32.py: -------------------------------------------------------------------------------- 1 | from Adafruit_PureIO.smbus import SMBus # pip install adafruit-blinka 2 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.packer import Packer 3 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.unpacker import Unpacker 4 | import time 5 | 6 | 7 | slave_address = 0x04 # slave address is 4 8 | register = 0x01 # register to read is 0x01 9 | 10 | 11 | def read_from_rpi_to_esp32(): 12 | try: 13 | # change 1 of SMBus(1) to bus number on your RPI 14 | smbus = SMBus(1) 15 | # prepare the data 16 | packed = None 17 | with Packer() as packer: 18 | packer.write(register) 19 | packer.end() 20 | packed = packer.read() 21 | 22 | raw_list = None 23 | smbus.write_bytes(register, bytearray(packed)) 24 | time.sleep(0.3) # wait i2c process the request 25 | raw_list = list(smbus.read_bytes(register, 5)) # the read_bytes contains the data format: first, length, data, crc8, end bytes 26 | print(raw_list) 27 | 28 | # let's clean received data 29 | unpacked = None 30 | with Unpacker as unpacker: 31 | unpacker.write(raw_list) 32 | unpacked = unpacker.read() 33 | 34 | return unpacked 35 | 36 | except Exception as e: 37 | print("ERROR: {}".format(e)) 38 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [3.7, 3.8] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install flake8 pytest 30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 31 | - name: Lint with flake8 32 | run: | 33 | # stop the build if there are Python syntax errors or undefined names 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | - name: Test with pytest 38 | run: | 39 | pytest 40 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # this file is *not* meant to cover or endorse the use of tox or pytest or 2 | # testing in general, 3 | # 4 | # It's meant to show the use of: 5 | # 6 | # - check-manifest 7 | # confirm items checked into vcs are in your sdist 8 | # - python setup.py check 9 | # confirm required package meta-data in setup.py 10 | # - readme_renderer (when using a ReStructuredText README) 11 | # confirms your long_description will render correctly on PyPI. 12 | # 13 | # and also to help confirm pull requests to this project. 14 | 15 | [tox] 16 | envlist = py{37,38} 17 | 18 | # Define the minimal tox version required to run; 19 | # if the host tox is less than this the tool with create an environment and 20 | # provision it with a tox that satisfies it under provision_tox_env. 21 | # At least this version is needed for PEP 517/518 support. 22 | minversion = 3.18.0 23 | 24 | # Activate isolated build environment. tox will use a virtual environment 25 | # to build a source distribution from the source tree. For build tools and 26 | # arguments use the pyproject.toml file as specified in PEP-517 and PEP-518. 27 | isolated_build = true 28 | 29 | [testenv] 30 | deps = 31 | check-manifest >= 0.42 32 | # If your project uses README.rst, uncomment the following: 33 | # readme_renderer 34 | flake8 35 | pytest 36 | commands = 37 | # check-manifest --ignore 'tox.ini,tests/**' 38 | # This repository uses a Markdown long_description, so the -r flag to 39 | # `setup.py check` is not needed. If your project contains a README.rst, 40 | # use `python setup.py check -m -r -s` instead. 41 | python setup.py check -m -s 42 | flake8 43 | py.test {posargs} 44 | 45 | [flake8] 46 | exclude = .tox,*.egg,build,data,docs,venv 47 | select = E,W,F 48 | ignore = W503, E402, F401, E501 49 | 50 | [check-manifest] 51 | ignore = .pypirc,tox.ini,tests/,docs/,venv/ -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | from setuptools import setup, find_packages 4 | 5 | # Package meta-data. 6 | NAME = "raspberrypi-esp32-i2c" 7 | DESCRIPTION = "use Raspberry Pi as master on ESP32 i2c \ 8 | slave when use ESP32 i2c Slave c++ library" 9 | URL = "https://github.com/MkLHX/Raspberry_Pi_Master_for_ESP32_I2C_SLAVE" 10 | EMAIL = "mickael@lehoux.net" 11 | AUTHOR = "Mickael Lehoux" 12 | REQUIRES_PYTHON = ">=3.6.0" 13 | VERSION = "0.0.6" 14 | 15 | # What packages are required for this module to be executed? 16 | REQUIRED = [ 17 | # 'requests', 'maya', 'records', 18 | ] 19 | 20 | # What packages are optional? 21 | EXTRAS = { 22 | # 'fancy feature': ['django'], 23 | } 24 | 25 | here = pathlib.Path(__file__).parent.resolve() 26 | 27 | # Get the long description from the README file 28 | long_description = (here / "README.md").read_text(encoding="utf-8") 29 | 30 | # Load the package's version.py module as a dictionary. 31 | about = {} 32 | if not VERSION: 33 | with open(os.path.join(here, "version.py")) as f: 34 | exec(f.read(), about) 35 | else: 36 | about["__version__"] = VERSION 37 | 38 | setup( 39 | name=NAME, 40 | version=about["__version__"], 41 | author=AUTHOR, 42 | author_email=EMAIL, 43 | description=DESCRIPTION, 44 | long_description=long_description, 45 | long_description_content_type="text/markdown", 46 | url=URL, 47 | license="MIT", 48 | install_requires=REQUIRED, 49 | classifiers=[ 50 | "Programming Language :: Python :: 3", 51 | "License :: OSI Approved :: MIT License", 52 | "Operating System :: OS Independent", 53 | ], 54 | packages=find_packages(), 55 | python_requires=REQUIRES_PYTHON, 56 | project_urls={ # Optional 57 | "Source": "https://github.com/MkLHX/\ 58 | Raspberry_Pi_Master_for_ESP32_I2C_SLAVE", 59 | "Bug Reports": "https://github.com/MkLHX/\ 60 | Raspberry_Pi_Master_for_ESP32_I2C_SLAVE/issues", 61 | }, 62 | keywords="i2c driver python hardware diy iot raspberry pi", 63 | ) 64 | -------------------------------------------------------------------------------- /example/esp32_slave_side.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file esp32_slave_side.cpp 3 | * @author MkLHX https://github.com/MkLHX 4 | * @brief 5 | * @date 2021-04-20 6 | * 7 | * @copyright Copyright (c) 2021 8 | * this is an example of esp32 slave side c++ code 9 | */ 10 | 11 | #include 12 | #ifndef Wire 13 | #include 14 | #endif 15 | #ifndef WireSlave 16 | #include 17 | #endif 18 | 19 | void receiveEvent(int numBytes); 20 | void requestEvent(void); 21 | 22 | byte i2cAddr = 0x21 // or wathever 23 | int howManyBytesReceived; 24 | uint8_t registerCode; 25 | 26 | void receiveEvent(int numBytes) 27 | { 28 | 29 | Serial.print(F("---> recieved ")); 30 | Serial.print(numBytes); 31 | howManyBytesReceived = numBytes; 32 | Serial.println(F(" events on i2c bus")); 33 | registerCode = WireSlave.read(); 34 | } 35 | 36 | void requestEvent(void) 37 | { 38 | Serial.print(F("---> request register value: ")); 39 | Serial.print(registerCode, DEC); 40 | Serial.print(F(" / 0x0")); 41 | Serial.println(registerCode, HEX); 42 | 43 | // switch case for multiple i2c registers 44 | switch (registerCode) 45 | { 46 | case 0x00: 47 | { 48 | Serial.print("case of register 0x00"); 49 | // return value about register 0x00 uint8_t format 50 | const uint8_t device_type = 0x00; 51 | WireSlave.write(device_type); 52 | break; 53 | } 54 | case 0x01: 55 | { 56 | Serial.print("case of register 0x01"); 57 | // return value about register 0x01 uint8_t format 58 | const uint8_t device_firmware = 10; // send 10 divide by 10 to get the real value 59 | WireSlave.write(device_firmware); 60 | break; 61 | } 62 | default { 63 | Serial.print("UNKNOW REGISTER: "); 64 | Serial.println(registerCode); 65 | } 66 | } 67 | } 68 | 69 | void setup() 70 | { 71 | Serial.begin(115200); 72 | bool ready = WireSlave.begin(I2C_SDA_PIN, I2C_SCL_PIN, i2cAddr); 73 | if (!ready) 74 | { 75 | Serial.println(F("I2C slave init failed")); 76 | while (true) 77 | delay(100); 78 | } 79 | WireSlave.onReceive(receiveEvent); // receive register value 80 | WireSlave.onRequest(requestEvent); // send register value 81 | } 82 | 83 | void loop() 84 | { 85 | // your other logic than i2c here 86 | WireSlave.update(); 87 | delay(5); // cadencing loop to allow other i2c device interrupt 88 | } 89 | -------------------------------------------------------------------------------- /Raspberry_Pi_Master_for_ESP32_I2C_SLAVE/unpacker.py: -------------------------------------------------------------------------------- 1 | """ 2 | @file Unpacker.py 3 | @author Mickael Lehoux 4 | @brief Class to allow raspberry I2C Master to deal with ESP32 5 | using ESP32 Slave I2C library 6 | @date 2020-09-25 7 | 8 | The ESP32 Slave I2C library 9 | use packing and upacking classes to format data 10 | On python side we need to DECODE data from i2c 11 | 12 | format: 13 | [0]: start byte (0x02) 14 | [1]: packet length 15 | [2]: data[0] 16 | [3]: data[1] 17 | ... 18 | [n+1]: data[n-1] 19 | [n+2]: CRC8 of packet length and data 20 | [n+3]: end byte (0x04) 21 | based on: 22 | https://github.com/gutierrezps/ESP32_I2C_Slave/blob/master/src/WireUnpacker.h 23 | https://github.com/gutierrezps/ESP32_I2C_Slave/blob/master/src/WireUnpacker.cpp 24 | """ 25 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.crc8 import Crc8 26 | 27 | 28 | class Unpacker: 29 | error_codes = { 30 | "INVALID_CRC": 1, 31 | "INVALID_LENGTH": 2, 32 | "INVALID_START": 5, 33 | "INVALID_END": 6, 34 | } 35 | error_decodes = { 36 | 1: "INVALID_CRC", 37 | 2: "INVALID_LENGTH", 38 | 3: "INVALID_START", 39 | 4: "INVALID_END", 40 | } 41 | 42 | def __init__(self): 43 | self._debug = False 44 | self.reset() 45 | 46 | @property 47 | def debug(self): 48 | return self._debug 49 | 50 | @debug.setter 51 | def debug(self, d): 52 | self._debug = d 53 | 54 | def __enter__(self): 55 | """Context manager enter function.""" 56 | # Just return this object so it can be used in a with statement, like 57 | # with Unpacker() as unpacker: 58 | # # do stuff! 59 | return self 60 | 61 | def __exit__(self, exc_type, exc_val, exc_tb): 62 | """Context manager exit function, ensures resources are cleaned up.""" 63 | return False # Don't suppress exceptions. 64 | 65 | def __del__(self): 66 | """Clean up any resources instance.""" 67 | return False 68 | 69 | def get_last_error(self): 70 | """ 71 | @brief get the last error code and message 72 | @return list [error_code, error_text] 73 | """ 74 | return self._last_error, self.error_decodes[self._last_error] 75 | 76 | def read(self): 77 | """ 78 | @brief return i2c values from slave 79 | @return list buffer 80 | """ 81 | return self._buffer 82 | 83 | def write(self, data: list): 84 | """ 85 | @brief get the i2c data from slave 86 | check if start, end and crc8 bytes are correct 87 | remove start, length, crc8 and end bytes 88 | """ 89 | if self._debug: 90 | print("Data to unpack: ", data) 91 | if data[0] != self._frame_start: 92 | self._last_error = self.error_codes["INVALID_START"] 93 | raise Exception("ERROR: Unpacker invalid start byte") 94 | if data[-1] != self._frame_end: 95 | self._last_error = self.error_codes["INVALID_END"] 96 | raise Exception("ERROR: Unpacker invalid end byte") 97 | 98 | # check if provided crc8 is good 99 | # ignore crc and end bytes 100 | payload_range = len(data) - 2 101 | crc8 = Crc8() 102 | # ignore start, length 103 | crc = crc8.calc(data[2:payload_range]) 104 | if crc != data[-2]: 105 | self._last_error = self.error_codes["INVALID_CRC"] 106 | raise Exception("ERROR: Unpacker invalid crc8") 107 | 108 | self._buffer = data[2:-2] 109 | 110 | def reset(self): 111 | self._frame_start = 0x02 112 | self._frame_end = 0x04 113 | self._buffer = [] 114 | self._last_error = None 115 | -------------------------------------------------------------------------------- /Raspberry_Pi_Master_for_ESP32_I2C_SLAVE/packer.py: -------------------------------------------------------------------------------- 1 | """ 2 | @file Packer.py 3 | @author Mickael Lehoux 4 | @brief Class to allow raspberry I2C Master to deal with ESP32 5 | using ESP32 Slave I2C library 6 | @date 2020-09-18 7 | 8 | The ESP32 Slave I2C library 9 | use packing and upacking classes to format data 10 | On python side we need to ENCODE data before send them through i2c 11 | 12 | Packet format: 13 | [0]: start byte (0x02) 14 | [1]: packet length 15 | [2]: data[0] 16 | [3]: data[1] 17 | ... 18 | [n+1]: data[n-1] 19 | [n+2]: CRC8 of packet length and data 20 | [n+3]: end byte (0x04) 21 | based on: 22 | https://github.com/gutierrezps/ESP32_I2C_Slave/blob/master/src/WirePacker.h 23 | https://github.com/gutierrezps/ESP32_I2C_Slave/blob/master/src/WirePacker.cpp 24 | """ 25 | from Raspberry_Pi_Master_for_ESP32_I2C_SLAVE.crc8 import Crc8 26 | 27 | 28 | class Packer: 29 | PACKER_BUFFER_LENGTH = ( 30 | 128 # because ESP Slave I2C library wait for buffer[128] size 31 | ) 32 | 33 | def __init__(self): 34 | self._debug = False 35 | self._buffer = [0] * self.PACKER_BUFFER_LENGTH 36 | self._index = 0 37 | self.reset() 38 | 39 | @property 40 | def debug(self): 41 | return self._debug 42 | 43 | @debug.setter 44 | def debug(self, d): 45 | self._debug = d 46 | 47 | def __enter__(self): 48 | """Context manager enter function.""" 49 | # Just return this object so it can be used in a with statement, like 50 | # with Packer() as packer: 51 | # # do stuff! 52 | return self 53 | 54 | def __exit__(self, exc_type, exc_val, exc_tb): 55 | """Context manager exit function, ensures resources are cleaned up.""" 56 | self.reset() 57 | return False # Don't suppress exceptions. 58 | 59 | def __del__(self): 60 | """Clean up any resources instance.""" 61 | return False 62 | 63 | def read(self): 64 | """ 65 | Read the next available packet byte. At each call, 66 | the value returned by available() will be decremented. 67 | @return int -1 if no bytes to read / byte value 68 | """ 69 | if not self._is_written: 70 | raise Exception( 71 | "You need to finish process by using packer.end() method before read buffer" 72 | ) 73 | return self._buffer 74 | 75 | def write(self, data: int): 76 | """ 77 | @brief write data in prepared buffer 78 | @param int data 79 | """ 80 | if self._debug: 81 | print("Data to unpack: ", data) 82 | if self._is_written: # allow write after .end() 83 | self._is_written = False 84 | self._buffer[self._index] = data 85 | self._index += 1 86 | 87 | def end(self): 88 | """ 89 | @brief Closes the packet. After that, use avaiable() and read() 90 | to get the packet bytes. 91 | """ 92 | self._index += 1 # add field for CRC byte 93 | self._buffer[self._index] = self._frame_end 94 | self._index += 1 95 | self._total_length = self._index 96 | self._buffer[1] = self._total_length 97 | 98 | # ignore crc and end bytes 99 | payload_range = self._total_length - 2 100 | 101 | # ignore start and length bytes [2:payload_range] 102 | crc = Crc8() 103 | _crc8 = crc.calc(self._buffer[2:payload_range]) 104 | 105 | self._buffer[self._index - 2] = _crc8 106 | self._is_written = True 107 | 108 | def reset(self): 109 | """ 110 | @brief Reset the packing process. 111 | """ 112 | self._frame_start = 0x02 113 | self._frame_end = 0x04 114 | self._buffer[0] = self._frame_start 115 | self._index = 2 # keep field for total lenght on index 1 116 | self._is_written = False 117 | --------------------------------------------------------------------------------