├── 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 |
--------------------------------------------------------------------------------