From 86f2b3572c930ae44c5e84be1b0c508fd2c6b865 Mon Sep 17 00:00:00 2001 From: Thomas Franks Date: Thu, 4 Aug 2022 11:42:01 -0400 Subject: [PATCH 1/7] Annotation and hinting for BitbangIO --- adafruit_bitbangio.py | 101 +++++++++++++++++++++++------------------- requirements.txt | 1 + setup.py | 1 + 3 files changed, 57 insertions(+), 46 deletions(-) diff --git a/adafruit_bitbangio.py b/adafruit_bitbangio.py index 92d2b17..6696d84 100644 --- a/adafruit_bitbangio.py +++ b/adafruit_bitbangio.py @@ -22,6 +22,15 @@ https://github.com/adafruit/circuitpython/releases """ +import microcontroller + +try: + from typing import Optional + from typing_extensions import Literal + from circuitpython_typing import WriteableBuffer, ReadableBuffer + from microcontroller import Pin +except ImportError: + pass # imports from time import monotonic @@ -37,24 +46,24 @@ class _BitBangIO: """Base class for subclassing only""" - def __init__(self): + def __init__(self) -> None: self._locked = False - def try_lock(self): + def try_lock(self) -> bool: """Attempt to grab the lock. Return True on success, False if the lock is already taken.""" if self._locked: return False self._locked = True return True - def unlock(self): + def unlock(self) -> None: """Release the lock so others may use the resource.""" if self._locked: self._locked = False else: raise ValueError("Not locked") - def _check_lock(self): + def _check_lock(self) -> Optional[bool]: if not self._locked: raise RuntimeError("First call try_lock()") return True @@ -62,11 +71,11 @@ def _check_lock(self): def __enter__(self): return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type, exc_value, traceback) -> None: self.deinit() # pylint: disable=no-self-use - def deinit(self): + def deinit(self) -> None: """Free any hardware used by the object.""" return @@ -76,7 +85,7 @@ def deinit(self): class I2C(_BitBangIO): """Software-based implementation of the I2C protocol over GPIO pins.""" - def __init__(self, scl, sda, *, frequency=400000, timeout=1): + def __init__(self, scl: microcontroller.Pin, sda: microcontroller.Pin, *, frequency: int = 400000, timeout: int = 1) -> None: """Initialize bitbang (or software) based I2C. Must provide the I2C clock, and data pin numbers. """ @@ -95,17 +104,17 @@ def __init__(self, scl, sda, *, frequency=400000, timeout=1): self._delay = (1 / frequency) / 2 # half period self._timeout = timeout - def deinit(self): + def deinit(self) -> None: """Free any hardware used by the object.""" self._sda.deinit() self._scl.deinit() - def _wait(self): + def _wait(self) -> None: end = monotonic() + self._delay # half period while end > monotonic(): pass - def scan(self): + def scan(self) -> List[int]: """Perform an I2C Device Scan""" found = [] if self._check_lock(): @@ -114,14 +123,14 @@ def scan(self): found.append(address) return found - def writeto(self, address, buffer, *, start=0, end=None): + def writeto(self, address: int, buffer: ReadableBuffer, *, start=0, end=None) -> None: """Write data from the buffer to an address""" if end is None: end = len(buffer) if self._check_lock(): self._write(address, buffer[start:end], True) - def readfrom_into(self, address, buffer, *, start=0, end=None): + def readfrom_into(self, address: int, buffer: WriteableBuffer, *, start: int = 0, end: Optional[int] = None) -> None: """Read data from an address and into the buffer""" if end is None: end = len(buffer) @@ -133,15 +142,15 @@ def readfrom_into(self, address, buffer, *, start=0, end=None): def writeto_then_readfrom( self, - address, - buffer_out, - buffer_in, + address: int, + buffer_out: ReadableBuffer, + buffer_in: WriteableBuffer, *, - out_start=0, - out_end=None, - in_start=0, - in_end=None - ): + out_start: int = 0, + out_end: Optional[int] = None, + in_start: int = 0, + in_end: Optional[int] = None + ) -> None: """Write data from buffer_out to an address and then read data from an address and into buffer_in """ @@ -153,30 +162,30 @@ def writeto_then_readfrom( self._write(address, buffer_out[out_start:out_end], False) self.readfrom_into(address, buffer_in, start=in_start, end=in_end) - def _scl_low(self): + def _scl_low(self) -> None: self._scl.switch_to_output(value=False) - def _sda_low(self): + def _sda_low(self) -> None: self._sda.switch_to_output(value=False) - def _scl_release(self): + def _scl_release(self) -> None: """Release and let the pullups lift""" # Use self._timeout to add clock stretching self._scl.switch_to_input() - def _sda_release(self): + def _sda_release(self) -> None: """Release and let the pullups lift""" # Use self._timeout to add clock stretching self._sda.switch_to_input() - def _start(self): + def _start(self) -> None: self._sda_release() self._scl_release() self._wait() self._sda_low() self._wait() - def _stop(self): + def _stop(self) -> None: self._scl_low() self._wait() self._sda_low() @@ -186,7 +195,7 @@ def _stop(self): self._sda_release() self._wait() - def _repeated_start(self): + def _repeated_start(self) -> None: self._scl_low() self._wait() self._sda_release() @@ -196,7 +205,7 @@ def _repeated_start(self): self._sda_low() self._wait() - def _write_byte(self, byte): + def _write_byte(self, byte: int ) -> bool: for bit_position in range(8): self._scl_low() @@ -222,7 +231,7 @@ def _write_byte(self, byte): return not ack - def _read_byte(self, ack=False): + def _read_byte(self, ack: bool = False) -> int: self._scl_low() self._wait() # sda will already be an input as we are simulating open drain @@ -246,13 +255,13 @@ def _read_byte(self, ack=False): return data & 0xFF - def _probe(self, address): + def _probe(self, address: int) -> bool: self._start() ok = self._write_byte(address << 1) self._stop() return ok > 0 - def _write(self, address, buffer, transmit_stop): + def _write(self, address: int, buffer: ReadableBuffer, transmit_stop: bool) -> None: self._start() if not self._write_byte(address << 1): raise RuntimeError("Device not responding at 0x{:02X}".format(address)) @@ -261,7 +270,7 @@ def _write(self, address, buffer, transmit_stop): if transmit_stop: self._stop() - def _read(self, address, length): + def _read(self, address: int, length: int) -> ReadableBuffer: self._start() if not self._write_byte(address << 1 | 1): raise RuntimeError("Device not responding at 0x{:02X}".format(address)) @@ -275,7 +284,7 @@ def _read(self, address, length): class SPI(_BitBangIO): """Software-based implementation of the SPI protocol over GPIO pins.""" - def __init__(self, clock, MOSI=None, MISO=None): + def __init__(self, clock: microcontroller.Pin, MOSI: Optional[microcontroller.Pin] = None, MISO: Optional[microcontroller.Pin] = None) -> None: """Initialize bit bang (or software) based SPI. Must provide the SPI clock, and optionally MOSI and MISO pin numbers. If MOSI is set to None then writes will be disabled and fail with an error, likewise for MISO @@ -304,7 +313,7 @@ def __init__(self, clock, MOSI=None, MISO=None): self._miso = DigitalInOut(MISO) self._miso.switch_to_input() - def deinit(self): + def deinit(self) -> None: """Free any hardware used by the object.""" self._sclk.deinit() if self._miso: @@ -312,7 +321,7 @@ def deinit(self): if self._mosi: self._mosi.deinit() - def configure(self, *, baudrate=100000, polarity=0, phase=0, bits=8): + def configure(self, *, baudrate: int = 100000, polarity: Literal[0,1] = 0, phase: Literal[0,1] = 0, bits: int = 8) -> None: """Configures the SPI bus. Only valid when locked.""" if self._check_lock(): if not isinstance(baudrate, int): @@ -331,13 +340,13 @@ def configure(self, *, baudrate=100000, polarity=0, phase=0, bits=8): self._bits = bits self._half_period = (1 / self._baudrate) / 2 # 50% Duty Cyle delay - def _wait(self, start=None): + def _wait(self, start: Optional[int] = None) -> float: """Wait for up to one half cycle""" while (start + self._half_period) > monotonic(): pass return monotonic() # Return current time - def write(self, buffer, start=0, end=None): + def write(self, buffer: ReadableBuffer, start: int = 0, end: Optional[int] = None) -> None: """Write the data contained in buf. Requires the SPI being locked. If the buffer is empty, nothing happens. """ @@ -369,7 +378,7 @@ def write(self, buffer, start=0, end=None): self._sclk.value = self._polarity # pylint: disable=too-many-branches - def readinto(self, buffer, start=0, end=None, write_value=0): + def readinto(self, buffer: WriteableBuffer, start: int = 0, end: Optional[int] = None, write_value: int = 0) -> None: """Read into the buffer specified by buf while writing zeroes. Requires the SPI being locked. If the number of bytes to read is 0, nothing happens. """ @@ -417,14 +426,14 @@ def readinto(self, buffer, start=0, end=None, write_value=0): def write_readinto( self, - buffer_out, - buffer_in, + buffer_out: ReadableBuffer, + buffer_in: WriteableBuffer, *, - out_start=0, - out_end=None, - in_start=0, - in_end=None - ): + out_start: int = 0, + out_end: Optional[int] = None, + in_start: int = 0, + in_end: Optional[int] = None + ) -> None: """Write out the data in buffer_out while simultaneously reading data into buffer_in. The lengths of the slices defined by buffer_out[out_start:out_end] and buffer_in[in_start:in_end] must be equal. If buffer slice lengths are @@ -482,6 +491,6 @@ def write_readinto( # pylint: enable=too-many-branches @property - def frequency(self): + def frequency(self) -> int: """Return the currently configured baud rate""" return self._baudrate diff --git a/requirements.txt b/requirements.txt index 17a850d..333f22c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka +typing-extensions \ No newline at end of file diff --git a/setup.py b/setup.py index fe7664c..c6ccb36 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ author_email="circuitpython@adafruit.com", install_requires=[ "Adafruit-Blinka", + "typing-extensions", ], # Choose your license license="MIT", From e70b024a29973c989d246bd2c569afb5003a211c Mon Sep 17 00:00:00 2001 From: Thomas Franks Date: Thu, 4 Aug 2022 12:03:17 -0400 Subject: [PATCH 2/7] Annotation and hinting for BitbangIO --- adafruit_bitbangio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_bitbangio.py b/adafruit_bitbangio.py index 6696d84..f498cc2 100644 --- a/adafruit_bitbangio.py +++ b/adafruit_bitbangio.py @@ -22,7 +22,6 @@ https://github.com/adafruit/circuitpython/releases """ -import microcontroller try: from typing import Optional From e91d861aa24f4a18a51d2caf575ed3c3c1d311f5 Mon Sep 17 00:00:00 2001 From: Thomas Franks Date: Mon, 8 Aug 2022 16:13:21 -0400 Subject: [PATCH 3/7] Annotation and hinting for BitbangIO --- adafruit_bitbangio.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/adafruit_bitbangio.py b/adafruit_bitbangio.py index f498cc2..9864a46 100644 --- a/adafruit_bitbangio.py +++ b/adafruit_bitbangio.py @@ -24,7 +24,7 @@ """ try: - from typing import Optional + from typing import Optional, List from typing_extensions import Literal from circuitpython_typing import WriteableBuffer, ReadableBuffer from microcontroller import Pin @@ -62,7 +62,7 @@ def unlock(self) -> None: else: raise ValueError("Not locked") - def _check_lock(self) -> Optional[bool]: + def _check_lock(self) -> Literal[True]: if not self._locked: raise RuntimeError("First call try_lock()") return True @@ -84,7 +84,7 @@ def deinit(self) -> None: class I2C(_BitBangIO): """Software-based implementation of the I2C protocol over GPIO pins.""" - def __init__(self, scl: microcontroller.Pin, sda: microcontroller.Pin, *, frequency: int = 400000, timeout: int = 1) -> None: + def __init__(self, scl: Pin, sda: Pin, *, frequency: int = 400000, timeout: float = 1) -> None: """Initialize bitbang (or software) based I2C. Must provide the I2C clock, and data pin numbers. """ @@ -122,7 +122,7 @@ def scan(self) -> List[int]: found.append(address) return found - def writeto(self, address: int, buffer: ReadableBuffer, *, start=0, end=None) -> None: + def writeto(self, address: int, buffer: ReadableBuffer, *, start: int = 0, end: Optional[int] = None) -> None: """Write data from the buffer to an address""" if end is None: end = len(buffer) @@ -269,7 +269,7 @@ def _write(self, address: int, buffer: ReadableBuffer, transmit_stop: bool) -> N if transmit_stop: self._stop() - def _read(self, address: int, length: int) -> ReadableBuffer: + def _read(self, address: int, length: int) -> bytearray: self._start() if not self._write_byte(address << 1 | 1): raise RuntimeError("Device not responding at 0x{:02X}".format(address)) @@ -283,7 +283,7 @@ def _read(self, address: int, length: int) -> ReadableBuffer: class SPI(_BitBangIO): """Software-based implementation of the SPI protocol over GPIO pins.""" - def __init__(self, clock: microcontroller.Pin, MOSI: Optional[microcontroller.Pin] = None, MISO: Optional[microcontroller.Pin] = None) -> None: + def __init__(self, clock: Pin, MOSI: Optional[Pin] = None, MISO: Optional[Pin] = None) -> None: """Initialize bit bang (or software) based SPI. Must provide the SPI clock, and optionally MOSI and MISO pin numbers. If MOSI is set to None then writes will be disabled and fail with an error, likewise for MISO @@ -320,7 +320,7 @@ def deinit(self) -> None: if self._mosi: self._mosi.deinit() - def configure(self, *, baudrate: int = 100000, polarity: Literal[0,1] = 0, phase: Literal[0,1] = 0, bits: int = 8) -> None: + def configure(self, *, baudrate: int = 100000, polarity: Literal[0, 1] = 0, phase: Literal[0, 1] = 0, bits: int = 8) -> None: """Configures the SPI bus. Only valid when locked.""" if self._check_lock(): if not isinstance(baudrate, int): From 74c265d0e55938f8766a07ee25b51ee209f630e2 Mon Sep 17 00:00:00 2001 From: Thomas Franks Date: Tue, 9 Aug 2022 12:34:09 -0400 Subject: [PATCH 4/7] Annotation and hinting for BitbangIO --- adafruit_bitbangio.py | 49 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/adafruit_bitbangio.py b/adafruit_bitbangio.py index 9864a46..36e8626 100644 --- a/adafruit_bitbangio.py +++ b/adafruit_bitbangio.py @@ -84,7 +84,9 @@ def deinit(self) -> None: class I2C(_BitBangIO): """Software-based implementation of the I2C protocol over GPIO pins.""" - def __init__(self, scl: Pin, sda: Pin, *, frequency: int = 400000, timeout: float = 1) -> None: + def __init__( + self, scl: Pin, sda: Pin, *, frequency: int = 400000, timeout: float = 1 + ) -> None: """Initialize bitbang (or software) based I2C. Must provide the I2C clock, and data pin numbers. """ @@ -122,14 +124,28 @@ def scan(self) -> List[int]: found.append(address) return found - def writeto(self, address: int, buffer: ReadableBuffer, *, start: int = 0, end: Optional[int] = None) -> None: + def writeto( + self, + address: int, + buffer: ReadableBuffer, + *, + start: int = 0, + end: Optional[int] = None + ) -> None: """Write data from the buffer to an address""" if end is None: end = len(buffer) if self._check_lock(): self._write(address, buffer[start:end], True) - def readfrom_into(self, address: int, buffer: WriteableBuffer, *, start: int = 0, end: Optional[int] = None) -> None: + def readfrom_into( + self, + address: int, + buffer: WriteableBuffer, + *, + start: int = 0, + end: Optional[int] = None + ) -> None: """Read data from an address and into the buffer""" if end is None: end = len(buffer) @@ -204,7 +220,7 @@ def _repeated_start(self) -> None: self._sda_low() self._wait() - def _write_byte(self, byte: int ) -> bool: + def _write_byte(self, byte: int) -> bool: for bit_position in range(8): self._scl_low() @@ -283,7 +299,9 @@ def _read(self, address: int, length: int) -> bytearray: class SPI(_BitBangIO): """Software-based implementation of the SPI protocol over GPIO pins.""" - def __init__(self, clock: Pin, MOSI: Optional[Pin] = None, MISO: Optional[Pin] = None) -> None: + def __init__( + self, clock: Pin, MOSI: Optional[Pin] = None, MISO: Optional[Pin] = None + ) -> None: """Initialize bit bang (or software) based SPI. Must provide the SPI clock, and optionally MOSI and MISO pin numbers. If MOSI is set to None then writes will be disabled and fail with an error, likewise for MISO @@ -320,7 +338,14 @@ def deinit(self) -> None: if self._mosi: self._mosi.deinit() - def configure(self, *, baudrate: int = 100000, polarity: Literal[0, 1] = 0, phase: Literal[0, 1] = 0, bits: int = 8) -> None: + def configure( + self, + *, + baudrate: int = 100000, + polarity: Literal[0, 1] = 0, + phase: Literal[0, 1] = 0, + bits: int = 8 + ) -> None: """Configures the SPI bus. Only valid when locked.""" if self._check_lock(): if not isinstance(baudrate, int): @@ -345,7 +370,9 @@ def _wait(self, start: Optional[int] = None) -> float: pass return monotonic() # Return current time - def write(self, buffer: ReadableBuffer, start: int = 0, end: Optional[int] = None) -> None: + def write( + self, buffer: ReadableBuffer, start: int = 0, end: Optional[int] = None + ) -> None: """Write the data contained in buf. Requires the SPI being locked. If the buffer is empty, nothing happens. """ @@ -377,7 +404,13 @@ def write(self, buffer: ReadableBuffer, start: int = 0, end: Optional[int] = Non self._sclk.value = self._polarity # pylint: disable=too-many-branches - def readinto(self, buffer: WriteableBuffer, start: int = 0, end: Optional[int] = None, write_value: int = 0) -> None: + def readinto( + self, + buffer: WriteableBuffer, + start: int = 0, + end: Optional[int] = None, + write_value: int = 0, + ) -> None: """Read into the buffer specified by buf while writing zeroes. Requires the SPI being locked. If the number of bytes to read is 0, nothing happens. """ From bda8d8ab6e3568d90990116bfd610c7a52142762 Mon Sep 17 00:00:00 2001 From: Thomas Franks Date: Tue, 9 Aug 2022 14:52:54 -0400 Subject: [PATCH 5/7] Annotation and hinting for BitbangIO --- adafruit_bitbangio.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/adafruit_bitbangio.py b/adafruit_bitbangio.py index 36e8626..551f245 100644 --- a/adafruit_bitbangio.py +++ b/adafruit_bitbangio.py @@ -130,7 +130,7 @@ def writeto( buffer: ReadableBuffer, *, start: int = 0, - end: Optional[int] = None + end: Optional[int] = None, ) -> None: """Write data from the buffer to an address""" if end is None: @@ -144,7 +144,7 @@ def readfrom_into( buffer: WriteableBuffer, *, start: int = 0, - end: Optional[int] = None + end: Optional[int] = None, ) -> None: """Read data from an address and into the buffer""" if end is None: @@ -164,7 +164,7 @@ def writeto_then_readfrom( out_start: int = 0, out_end: Optional[int] = None, in_start: int = 0, - in_end: Optional[int] = None + in_end: Optional[int] = None, ) -> None: """Write data from buffer_out to an address and then read data from an address and into buffer_in @@ -279,7 +279,8 @@ def _probe(self, address: int) -> bool: def _write(self, address: int, buffer: ReadableBuffer, transmit_stop: bool) -> None: self._start() if not self._write_byte(address << 1): - raise RuntimeError("Device not responding at 0x{:02X}".format(address)) + # raise RuntimeError("Device not responding at 0x{:02X}".format(address)) + raise RuntimeError(f"Device not responding at 0x{address:02X}") for byte in buffer: self._write_byte(byte) if transmit_stop: @@ -288,7 +289,8 @@ def _write(self, address: int, buffer: ReadableBuffer, transmit_stop: bool) -> N def _read(self, address: int, length: int) -> bytearray: self._start() if not self._write_byte(address << 1 | 1): - raise RuntimeError("Device not responding at 0x{:02X}".format(address)) + # raise RuntimeError("Device not responding at 0x{:02X}".format(address)) + raise RuntimeError(f"Device not responding at 0x{address:02X}") buffer = bytearray(length) for byte_position in range(length): buffer[byte_position] = self._read_byte(ack=(byte_position != length - 1)) @@ -344,7 +346,7 @@ def configure( baudrate: int = 100000, polarity: Literal[0, 1] = 0, phase: Literal[0, 1] = 0, - bits: int = 8 + bits: int = 8, ) -> None: """Configures the SPI bus. Only valid when locked.""" if self._check_lock(): @@ -464,7 +466,7 @@ def write_readinto( out_start: int = 0, out_end: Optional[int] = None, in_start: int = 0, - in_end: Optional[int] = None + in_end: Optional[int] = None, ) -> None: """Write out the data in buffer_out while simultaneously reading data into buffer_in. The lengths of the slices defined by buffer_out[out_start:out_end] and From 8fba5aaad5b0ba4766ac5f6389bb85c66d778731 Mon Sep 17 00:00:00 2001 From: Thomas Franks <69873214+tcfranks@users.noreply.github.com> Date: Tue, 9 Aug 2022 14:55:53 -0400 Subject: [PATCH 6/7] Delete setup.py --- setup.py | 56 -------------------------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index c6ccb36..0000000 --- a/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -"""A setuptools based setup module. - -See: -https://packaging.python.org/en/latest/distributing.html -https://github.com/pypa/sampleproject -""" - -from setuptools import setup, find_packages - -# To use a consistent encoding -from codecs import open -from os import path - -here = path.abspath(path.dirname(__file__)) - -# Get the long description from the README file -with open(path.join(here, "README.rst"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="adafruit-circuitpython-bitbangio", - use_scm_version=True, - setup_requires=["setuptools_scm"], - description="A library for adding bitbang I2C and SPI to CircuitPython without the built-in bitbangio module", - long_description=long_description, - long_description_content_type="text/x-rst", - # The project's main homepage. - url="https://github.com/adafruit/Adafruit_CircuitPython_BitbangIO", - # Author details - author="Adafruit Industries", - author_email="circuitpython@adafruit.com", - install_requires=[ - "Adafruit-Blinka", - "typing-extensions", - ], - # Choose your license - license="MIT", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries", - "Topic :: System :: Hardware", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - ], - # What does your project relate to? - keywords="adafruit blinka circuitpython micropython bitbangio bitbang spi i2c software", - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - py_modules=["adafruit_bitbangio"], -) From 96c9d07d3762f23076128385dc5d9a13a6b07d07 Mon Sep 17 00:00:00 2001 From: Thomas Franks Date: Wed, 10 Aug 2022 10:17:41 -0400 Subject: [PATCH 7/7] Annotation and hinting for BitbangIO --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c98d059..452ef26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka -typing-extensions \ No newline at end of file +typing-extensions