diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..21c125c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +.py text eol=lf +.rst text eol=lf +.txt text eol=lf +.yaml text eol=lf +.toml text eol=lf +.license text eol=lf +.md text eol=lf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70ade69..ff19dde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,42 +1,21 @@ -# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: Unlicense repos: - - repo: https://github.com/python/black - rev: 23.3.0 - hooks: - - id: black - - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 - hooks: - - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/pylint - rev: v2.17.4 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 hooks: - - id: pylint - name: pylint (library code) - types: [python] - args: - - --disable=consider-using-f-string - exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint - name: pylint (example code) - description: Run pylint rules on "examples/*.py" files - types: [python] - files: "^examples/" - args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code - - id: pylint - name: pylint (test code) - description: Run pylint rules on "tests/*.py" files - types: [python] - files: "^tests/" - args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - id: ruff-format + - id: ruff + args: ["--fix"] + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.1 + hooks: + - id: reuse diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index f945e92..0000000 --- a/.pylintrc +++ /dev/null @@ -1,399 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the ignore-list. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint.extensions.no_self_use - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call -disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=board - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format=LF - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=12 - - -[BASIC] - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=11 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.Exception diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 33c2a61..255dafd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,8 +8,11 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: python: "3" diff --git a/README.rst b/README.rst index 8d50913..f426b6a 100644 --- a/README.rst +++ b/README.rst @@ -13,9 +13,9 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_ble/actions :alt: Build Status -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: Black +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Code Style: Ruff This module provides higher-level BLE (Bluetooth Low Energy) functionality, building on the native `_bleio` module. diff --git a/adafruit_ble/__init__.py b/adafruit_ble/__init__.py index 4e5e660..69f7d5d 100755 --- a/adafruit_ble/__init__.py +++ b/adafruit_ble/__init__.py @@ -12,29 +12,28 @@ from __future__ import annotations -# pylint: disable=wrong-import-position - import sys if sys.implementation.name == "circuitpython" and sys.implementation.version[0] <= 4: raise ImportError( "This release is not compatible with CircuitPython 4.x; use library release 1.x.x" ) -# pylint: enable=wrong-import-position import _bleio -from .services import Service from .advertising import Advertisement +from .services import Service try: - from typing import Iterator, NoReturn, Optional, Tuple, Type, TYPE_CHECKING, Union + from typing import TYPE_CHECKING, Iterator, NoReturn, Optional, Tuple, Type, Union + from typing_extensions import Literal if TYPE_CHECKING: from circuitpython_typing import ReadableBuffer - from adafruit_ble.uuid import UUID + from adafruit_ble.characteristics import Characteristic + from adafruit_ble.uuid import UUID except ImportError: pass @@ -64,9 +63,7 @@ def _discover_remote(self, uuid: UUID) -> Optional[_bleio.Service]: if uuid in self._discovered_bleio_services: remote_service = self._discovered_bleio_services[uuid] else: - results = self._bleio_connection.discover_remote_services( - (uuid.bleio_uuid,) - ) + results = self._bleio_connection.discover_remote_services((uuid.bleio_uuid,)) if results: remote_service = results[0] self._discovered_bleio_services[uuid] = remote_service @@ -109,7 +106,7 @@ def __getitem__(self, key: Union[UUID, Type[Service]]) -> Optional[Service]: self._constructed_services[uuid] = constructed_service return constructed_service - raise KeyError("{!r} object has no service {}".format(self, key)) + raise KeyError(f"{self!r} object has no service {key}") @property def connected(self) -> bool: @@ -145,6 +142,9 @@ def pair(self, *, bond: bool = True) -> None: def disconnect(self) -> None: """Disconnect from peer.""" self._bleio_connection.disconnect() + # Clean up any services that need explicit cleanup. + for service in self._constructed_services.values(): + service.deinit() class BLERadio: @@ -193,12 +193,8 @@ def start_advertising( if scan_response: scan_response_bytes = bytes(scan_response) - # pylint: disable=unexpected-keyword-arg # Remove after 5.x is no longer supported. - if ( - sys.implementation.name == "circuitpython" - and sys.implementation.version[0] <= 5 - ): + if sys.implementation.name == "circuitpython" and sys.implementation.version[0] <= 5: if timeout is not None: raise NotImplementedError("timeout not available for CircuitPython 5.x") self._adapter.start_advertising( @@ -361,7 +357,7 @@ def address_bytes(self) -> bytes: @property def advertising(self) -> bool: """The advertising state""" - return self._adapter.advertising # pylint: disable=no-member + return self._adapter.advertising def _clean_connection_cache(self) -> None: """Remove cached connections that have disconnected.""" diff --git a/adafruit_ble/advertising/__init__.py b/adafruit_ble/advertising/__init__.py index 02324d3..562bd86 100644 --- a/adafruit_ble/advertising/__init__.py +++ b/adafruit_ble/advertising/__init__.py @@ -11,15 +11,14 @@ import struct try: - from typing import Dict, Any, Union, List, Optional, Type, TypeVar, TYPE_CHECKING + from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union + from typing_extensions import Literal if TYPE_CHECKING: from _bleio import ScanEntry - LazyObjectField_GivenClass = TypeVar( # pylint: disable=invalid-name - "LazyObjectField_GivenClass" - ) + LazyObjectField_GivenClass = TypeVar("LazyObjectField_GivenClass") except ImportError: pass @@ -27,17 +26,15 @@ def to_hex(seq: bytes) -> str: """Pretty prints a byte sequence as hex values.""" - return " ".join("{:02x}".format(v) for v in seq) + return " ".join(f"{v:02x}" for v in seq) def to_bytes_literal(seq: bytes) -> str: """Prints a byte sequence as a Python bytes literal that only uses hex encoding.""" - return 'b"' + "".join("\\x{:02x}".format(v) for v in seq) + '"' + return 'b"' + "".join(f"\\x{v:02x}" for v in seq) + '"' -def decode_data( - data: bytes, *, key_encoding: str = "B" -) -> Dict[Any, Union[bytes, List[bytes]]]: +def decode_data(data: bytes, *, key_encoding: str = "B") -> Dict[Any, Union[bytes, List[bytes]]]: """Helper which decodes length encoded structures into a dictionary with the given key encoding.""" i = 0 @@ -94,7 +91,6 @@ def encode_data( return bytes(data) -# pylint: disable=too-few-public-methods class AdvertisingDataField: """Top level class for any descriptor classes that live in Advertisement or its subclasses.""" @@ -106,13 +102,13 @@ def __init__(self, bit_position: int) -> None: self._bitmask = 1 << bit_position def __get__( - self, obj: Optional["AdvertisingFlags"], cls: Type["AdvertisingFlags"] - ) -> Union[bool, "AdvertisingFlag"]: + self, obj: Optional[AdvertisingFlags], cls: Type[AdvertisingFlags] + ) -> Union[bool, AdvertisingFlag]: if obj is None: return self return (obj.flags & self._bitmask) != 0 - def __set__(self, obj: "AdvertisingFlags", value: bool) -> None: + def __set__(self, obj: AdvertisingFlags, value: bool) -> None: if value: obj.flags |= self._bitmask else: @@ -130,9 +126,7 @@ class AdvertisingFlags(AdvertisingDataField): """BR/EDR not supported.""" # BR/EDR flags not included here, since we don't support BR/EDR. - def __init__( - self, advertisement: "Advertisement", advertising_data_type: int - ) -> None: + def __init__(self, advertisement: Advertisement, advertising_data_type: int) -> None: self._advertisement = advertisement self._adt = advertising_data_type self.flags = 0 @@ -164,15 +158,15 @@ def __init__(self, *, advertising_data_type: int) -> None: self._adt = advertising_data_type def __get__( - self, obj: Optional["Advertisement"], cls: Type["Advertisement"] - ) -> Optional[Union[str, "String"]]: + self, obj: Optional[Advertisement], cls: Type[Advertisement] + ) -> Optional[Union[str, String]]: if obj is None: return self if self._adt not in obj.data_dict: return None return str(obj.data_dict[self._adt], "utf-8") - def __set__(self, obj: "Advertisement", value: str) -> None: + def __set__(self, obj: Advertisement, value: str) -> None: obj.data_dict[self._adt] = value.encode("utf-8") @@ -184,15 +178,15 @@ def __init__(self, struct_format: str, *, advertising_data_type: int) -> None: self._adt = advertising_data_type def __get__( - self, obj: Optional["Advertisement"], cls: Type["Advertisement"] - ) -> Optional[Union[Any, "Struct"]]: + self, obj: Optional[Advertisement], cls: Type[Advertisement] + ) -> Optional[Union[Any, Struct]]: if obj is None: return self if self._adt not in obj.data_dict: return None return struct.unpack(self._format, obj.data_dict[self._adt])[0] - def __set__(self, obj: "Advertisement", value: Any) -> None: + def __set__(self, obj: Advertisement, value: Any) -> None: obj.data_dict[self._adt] = struct.pack(self._format, value) @@ -207,9 +201,7 @@ def __init__( self._adt = advertising_data_type self._kwargs = kwargs - def __get__( - self, obj: Optional["Advertisement"], cls: Type["Advertisement"] - ) -> Any: + def __get__(self, obj: Optional[Advertisement], cls: Type[Advertisement]) -> Any: if obj is None: return self # Return None if our object is immutable and the data is not present. @@ -274,7 +266,7 @@ def __init__(self, *, entry: Optional[ScanEntry] = None) -> None: if entry: self.data_dict = decode_data(entry.advertisement_bytes) self.address = entry.address - self._rssi = entry.rssi # pylint: disable=protected-access + self._rssi = entry.rssi self.connectable = entry.connectable self.scan_response = entry.scan_response self.mutable = False @@ -305,8 +297,7 @@ def get_prefix_bytes(cls) -> Optional[bytes]: b"" if cls.match_prefixes is None else b"".join( - len(prefix).to_bytes(1, "little") + prefix - for prefix in cls.match_prefixes + len(prefix).to_bytes(1, "little") + prefix for prefix in cls.match_prefixes ) ) @@ -353,13 +344,11 @@ def __str__(self) -> str: continue value = getattr(self, attr) if value is not None: - parts.append("{}={}".format(attr, str(value))) + parts.append(f"{attr}={str(value)}") return "<{} {} >".format(self.__class__.__name__, " ".join(parts)) def __len__(self) -> int: return compute_length(self.data_dict) def __repr__(self) -> str: - return "Advertisement(data={})".format( - to_bytes_literal(encode_data(self.data_dict)) - ) + return f"Advertisement(data={to_bytes_literal(encode_data(self.data_dict))})" diff --git a/adafruit_ble/advertising/adafruit.py b/adafruit_ble/advertising/adafruit.py index ad3285b..4f30c56 100755 --- a/adafruit_ble/advertising/adafruit.py +++ b/adafruit_ble/advertising/adafruit.py @@ -15,6 +15,7 @@ """ import struct + from micropython import const from . import Advertisement, LazyObjectField diff --git a/adafruit_ble/advertising/standard.py b/adafruit_ble/advertising/standard.py index 3c9ef74..7d22363 100644 --- a/adafruit_ble/advertising/standard.py +++ b/adafruit_ble/advertising/standard.py @@ -14,23 +14,24 @@ import struct from collections import OrderedDict, namedtuple +from ..uuid import StandardUUID, VendorUUID from . import ( Advertisement, AdvertisingDataField, - encode_data, + compute_length, decode_data, + encode_data, to_hex, - compute_length, ) -from ..uuid import StandardUUID, VendorUUID try: - from typing import Optional, List, Tuple, Union, Type, Iterator, Iterable, Any - from adafruit_ble.uuid import UUID - from adafruit_ble.characteristics import Characteristic - from adafruit_ble.services import Service + from typing import Any, Iterable, Iterator, List, Optional, Tuple, Type, Union + from _bleio import ScanEntry + from adafruit_ble.services import Service + from adafruit_ble.uuid import UUID + UsesServicesAdvertisement = Union[ "ProvideServicesAdvertisement", "SolicitServicesAdvertisement" ] @@ -51,7 +52,7 @@ def __init__( advertisement: UsesServicesAdvertisement, *, standard_services: List[int], - vendor_services: List[int] + vendor_services: List[int], ) -> None: self._advertisement = advertisement self._standard_service_fields = standard_services @@ -71,7 +72,7 @@ def __init__( uuid = VendorUUID(data[16 * i : 16 * (i + 1)]) self._vendor_services.append(uuid) - def __contains__(self, key: Union[UUID, Characteristic]) -> bool: + def __contains__(self, key: Union[UUID, Service]) -> bool: uuid = key if hasattr(key, "uuid"): uuid = key.uuid @@ -97,16 +98,10 @@ def __iter__(self) -> Iterator[UUID]: # TODO: Differentiate between complete and incomplete lists. def append(self, service: Service) -> None: """Append a service to the list.""" - if ( - isinstance(service.uuid, StandardUUID) - and service not in self._standard_services - ): + if isinstance(service.uuid, StandardUUID) and service not in self._standard_services: self._standard_services.append(service.uuid) self._update(self._standard_service_fields[0], self._standard_services) - elif ( - isinstance(service.uuid, VendorUUID) - and service not in self._vendor_services - ): + elif isinstance(service.uuid, VendorUUID) and service not in self._vendor_services: self._vendor_services.append(service.uuid) self._update(self._vendor_service_fields[0], self._vendor_services) @@ -122,10 +117,7 @@ def extend(self, services: Iterable[Service]) -> None: ): self._standard_services.append(service.uuid) standard = True - elif ( - isinstance(service.uuid, VendorUUID) - and service.uuid not in self._vendor_services - ): + elif isinstance(service.uuid, VendorUUID) and service.uuid not in self._vendor_services: self._vendor_services.append(service.uuid) vendor = True @@ -146,9 +138,7 @@ def __str__(self) -> str: class ServiceList(AdvertisingDataField): """Descriptor for a list of Service UUIDs that lazily binds a corresponding BoundServiceList.""" - def __init__( - self, *, standard_services: List[int], vendor_services: List[int] - ) -> None: + def __init__(self, *, standard_services: List[int], vendor_services: List[int]) -> None: self.standard_services = standard_services self.vendor_services = vendor_services @@ -244,7 +234,7 @@ def __init__( *, advertising_data_type: int = 0xFF, company_id: int, - key_encoding: str = "B" + key_encoding: str = "B", ) -> None: self._obj = obj self._company_id = company_id @@ -273,9 +263,7 @@ def __bytes__(self) -> bytes: def __str__(self) -> str: hex_data = to_hex(encode_data(self.data, key_encoding=self._key_encoding)) - return "".format( - self.company_id, hex_data - ) + return f"" class ManufacturerDataField: @@ -289,12 +277,8 @@ def __init__( # TODO: Support format strings that use numbers to repeat a given type. For now, we strip # numbers because Radio specifies string length with it. self.element_count = len(value_format.strip("> 1 and ( - not field_names or len(field_names) != self.element_count - ): - raise ValueError( - "Provide field_names when multiple values are in the format" - ) + if self.element_count > 1 and (not field_names or len(field_names) != self.element_count): + raise ValueError("Provide field_names when multiple values are in the format") self._entry_length = struct.calcsize(value_format) self.field_names = field_names if field_names: @@ -332,9 +316,7 @@ def __get__( def __set__(self, obj: "Advertisement", value: Any) -> None: if not obj.mutable: raise AttributeError() - if isinstance(value, tuple) and ( - self.element_count == 1 or isinstance(value[0], tuple) - ): + if isinstance(value, tuple) and (self.element_count == 1 or isinstance(value[0], tuple)): packed = bytearray(self._entry_length * len(value)) for i, entry in enumerate(value): offset = i * self._entry_length @@ -353,14 +335,14 @@ class ServiceData(AdvertisingDataField): """Encapsulates service data. It is read as a memoryview which can be manipulated or set as a bytearray to change the size.""" - def __init__(self, service: Characteristic) -> None: + def __init__(self, service: Service) -> None: if isinstance(service.uuid, StandardUUID): self._adt = 0x16 elif isinstance(service.uuid, VendorUUID): self._adt = 0x21 self._prefix = bytes(service.uuid) - def __get__( # pylint: disable=too-many-return-statements,too-many-branches + def __get__( self, obj: Optional[Service], cls: Type[Service] ) -> Optional[Union["ServiceData", memoryview]]: if obj is None: diff --git a/adafruit_ble/attributes/__init__.py b/adafruit_ble/attributes/__init__.py index 1f10dbd..77a77fd 100644 --- a/adafruit_ble/attributes/__init__.py +++ b/adafruit_ble/attributes/__init__.py @@ -10,6 +10,7 @@ specifically characteristics and descriptors. """ + import _bleio __version__ = "0.0.0+auto.0" @@ -48,7 +49,6 @@ class Attribute: security_mode: authenticated data signing, without man-in-the-middle protection """ - # pylint: disable=too-few-public-methods NO_ACCESS = _bleio.Attribute.NO_ACCESS OPEN = _bleio.Attribute.OPEN ENCRYPT_NO_MITM = _bleio.Attribute.ENCRYPT_NO_MITM diff --git a/adafruit_ble/characteristics/__init__.py b/adafruit_ble/characteristics/__init__.py index 42a5868..b212875 100644 --- a/adafruit_ble/characteristics/__init__.py +++ b/adafruit_ble/characteristics/__init__.py @@ -11,17 +11,19 @@ from __future__ import annotations import struct + import _bleio from ..attributes import Attribute try: - from typing import Optional, Type, Union, Tuple, Iterable, TYPE_CHECKING + from typing import TYPE_CHECKING, Iterable, Optional, Tuple, Type, Union if TYPE_CHECKING: from circuitpython_typing import ReadableBuffer - from adafruit_ble.uuid import UUID + from adafruit_ble.services import Service + from adafruit_ble.uuid import UUID except ImportError: pass @@ -107,9 +109,7 @@ def __init__( self.fixed_length = fixed_length self.initial_value = initial_value - def _ensure_bound( - self, service: Service, initial_value: Optional[bytes] = None - ) -> None: + def _ensure_bound(self, service: Service, initial_value: Optional[bytes] = None) -> None: """Binds the characteristic to the local Service or remote Characteristic object given.""" if self.field_name in service.bleio_characteristics: return @@ -263,7 +263,7 @@ def __init__( def __get__( self, obj: Optional[Service], cls: Optional[Type[Service]] = None - ) -> Optional[Union[Tuple, "StructCharacteristic"]]: + ) -> Optional[Union[Tuple, StructCharacteristic]]: if obj is None: return self raw_data = super().__get__(obj, cls) diff --git a/adafruit_ble/characteristics/float.py b/adafruit_ble/characteristics/float.py index 69915eb..bd01033 100644 --- a/adafruit_ble/characteristics/float.py +++ b/adafruit_ble/characteristics/float.py @@ -12,16 +12,16 @@ from __future__ import annotations -from . import Attribute -from . import StructCharacteristic +from . import Attribute, StructCharacteristic try: - from typing import Optional, Type, Union, TYPE_CHECKING + from typing import TYPE_CHECKING, Optional, Type, Union if TYPE_CHECKING: from circuitpython_typing import ReadableBuffer - from adafruit_ble.uuid import UUID + from adafruit_ble.services import Service + from adafruit_ble.uuid import UUID except ImportError: pass @@ -55,7 +55,7 @@ def __init__( def __get__( self, obj: Optional[Service], cls: Optional[Type[Service]] = None - ) -> Union[float, "FloatCharacteristic"]: + ) -> Union[float, FloatCharacteristic]: if obj is None: return self return super().__get__(obj)[0] diff --git a/adafruit_ble/characteristics/int.py b/adafruit_ble/characteristics/int.py index 52dbd80..3773bd6 100755 --- a/adafruit_ble/characteristics/int.py +++ b/adafruit_ble/characteristics/int.py @@ -12,16 +12,16 @@ from __future__ import annotations -from . import Attribute -from . import StructCharacteristic +from . import Attribute, StructCharacteristic try: - from typing import Optional, Type, Union, TYPE_CHECKING + from typing import TYPE_CHECKING, Optional, Type, Union if TYPE_CHECKING: from circuitpython_typing import ReadableBuffer - from adafruit_ble.uuid import UUID + from adafruit_ble.services import Service + from adafruit_ble.uuid import UUID except ImportError: pass @@ -63,7 +63,7 @@ def __init__( def __get__( self, obj: Optional[Service], cls: Optional[Type[Service]] = None - ) -> Union[int, "IntCharacteristic"]: + ) -> Union[int, IntCharacteristic]: if obj is None: return self return super().__get__(obj)[0] @@ -77,17 +77,13 @@ def __set__(self, obj: Service, value: int) -> None: class Int8Characteristic(IntCharacteristic): """Int8 number.""" - # pylint: disable=too-few-public-methods - def __init__( - self, *, min_value: int = -128, max_value: int = 127, **kwargs - ) -> None: + def __init__(self, *, min_value: int = -128, max_value: int = 127, **kwargs) -> None: super().__init__(" None: super().__init__(" No class Int16Characteristic(IntCharacteristic): """Int16 number.""" - # pylint: disable=too-few-public-methods - def __init__( - self, *, min_value: int = -32768, max_value: int = 32767, **kwargs - ) -> None: + def __init__(self, *, min_value: int = -32768, max_value: int = 32767, **kwargs) -> None: super().__init__(" None: + def __init__(self, *, min_value: int = 0, max_value: int = 0xFFFF, **kwargs) -> None: super().__init__(" None: @@ -125,8 +114,5 @@ def __init__( class Uint32Characteristic(IntCharacteristic): """Uint32 number.""" - # pylint: disable=too-few-public-methods - def __init__( - self, *, min_value: int = 0, max_value: int = 0xFFFFFFFF, **kwargs - ) -> None: + def __init__(self, *, min_value: int = 0, max_value: int = 0xFFFFFFFF, **kwargs) -> None: super().__init__(" Any: """Converts a utf-8 encoded JSON string into a python value.""" return json.loads(str(value, "utf-8")) - def __get__( - self, obj: Optional[Service], cls: Optional[Type[Service]] = None - ) -> Any: + def __get__(self, obj: Optional[Service], cls: Optional[Type[Service]] = None) -> Any: if obj is None: return self return self.unpack(super().__get__(obj, cls)) diff --git a/adafruit_ble/characteristics/stream.py b/adafruit_ble/characteristics/stream.py index 76d5b42..c3a75ef 100755 --- a/adafruit_ble/characteristics/stream.py +++ b/adafruit_ble/characteristics/stream.py @@ -15,18 +15,17 @@ import _bleio -from . import Attribute -from . import Characteristic -from . import ComplexCharacteristic +from . import Attribute, Characteristic, ComplexCharacteristic try: - from typing import Optional, Union, TYPE_CHECKING + from typing import TYPE_CHECKING, Optional, Union if TYPE_CHECKING: from circuitpython_typing import ReadableBuffer + from adafruit_ble.characteristics import Characteristic - from adafruit_ble.uuid import UUID from adafruit_ble.services import Service + from adafruit_ble.uuid import UUID except ImportError: pass @@ -66,10 +65,14 @@ def __init__( self._timeout = timeout self._buffer_size = buffer_size super().__init__( - uuid=uuid, properties=properties, read_perm=read_perm, write_perm=write_perm + uuid=uuid, + properties=properties, + read_perm=read_perm, + write_perm=write_perm, + max_length=buffer_size, ) - def bind(self, service: Service) -> Union[_bleio.Characteristic, BoundWriteStream]: + def bind(self, service: Service) -> Union[_bleio.CharacteristicBuffer, BoundWriteStream]: """Binds the characteristic to the given Service.""" bound_characteristic = super().bind(service) # If we're given a remote service then we're the client and need to buffer in. @@ -102,11 +105,10 @@ def __init__( properties=properties, read_perm=Attribute.NO_ACCESS, write_perm=write_perm, + max_length=buffer_size, ) - def bind( - self, service: Service - ) -> Union[_bleio.CharacteristicBuffer, BoundWriteStream]: + def bind(self, service: Service) -> Union[_bleio.CharacteristicBuffer, BoundWriteStream]: """Binds the characteristic to the given Service.""" bound_characteristic = super().bind(service) # If the service is remote need to write out. diff --git a/adafruit_ble/characteristics/string.py b/adafruit_ble/characteristics/string.py index 7326bc7..de9d33a 100755 --- a/adafruit_ble/characteristics/string.py +++ b/adafruit_ble/characteristics/string.py @@ -12,16 +12,16 @@ from __future__ import annotations -from . import Attribute -from . import Characteristic +from . import Attribute, Characteristic try: - from typing import Optional, Type, Union, TYPE_CHECKING + from typing import TYPE_CHECKING, Optional, Type, Union if TYPE_CHECKING: from circuitpython_typing import ReadableBuffer - from adafruit_ble.uuid import UUID + from adafruit_ble.services import Service + from adafruit_ble.uuid import UUID except ImportError: pass @@ -54,7 +54,7 @@ def __init__( def __get__( self, obj: Optional[Service], cls: Optional[Type[Service]] = None - ) -> Union[str, "StringCharacteristic"]: + ) -> Union[str, StringCharacteristic]: if obj is None: return self return str(super().__get__(obj, cls), "utf-8") @@ -66,9 +66,7 @@ def __set__(self, obj: Service, value: str) -> None: class FixedStringCharacteristic(Characteristic): """Fixed strings are set once when bound and unchanged after.""" - def __init__( - self, *, uuid: Optional[UUID] = None, read_perm: int = Attribute.OPEN - ) -> None: + def __init__(self, *, uuid: Optional[UUID] = None, read_perm: int = Attribute.OPEN) -> None: super().__init__( uuid=uuid, properties=Characteristic.READ, @@ -79,7 +77,7 @@ def __init__( def __get__( self, obj: Service, cls: Optional[Type[Service]] = None - ) -> Union[str, "FixedStringCharacteristic"]: + ) -> Union[str, FixedStringCharacteristic]: if obj is None: return self return str(super().__get__(obj, cls), "utf-8") diff --git a/adafruit_ble/services/__init__.py b/adafruit_ble/services/__init__.py index c246868..cebaeb7 100644 --- a/adafruit_ble/services/__init__.py +++ b/adafruit_ble/services/__init__.py @@ -44,10 +44,7 @@ def __init__( **initial_values, ) -> None: if service is None: - # pylint: disable=no-member - self.bleio_service = _bleio.Service( - self.uuid.bleio_uuid, secondary=secondary - ) + self.bleio_service = _bleio.Service(self.uuid.bleio_uuid, secondary=secondary) elif not service.remote: raise ValueError("Can only create services with a remote service or None") else: @@ -82,6 +79,9 @@ def __init__( else: getattr(self, class_attr) + def deinit(self): + """Override this method to do any explicit cleanup necessary on connection close.""" + @property def remote(self) -> bool: """True if the service is provided by a peer and accessed remotely.""" diff --git a/adafruit_ble/services/circuitpython.py b/adafruit_ble/services/circuitpython.py index 3d58b88..bc9079f 100755 --- a/adafruit_ble/services/circuitpython.py +++ b/adafruit_ble/services/circuitpython.py @@ -10,11 +10,11 @@ """ -from . import Service from ..characteristics import Characteristic from ..characteristics.stream import StreamOut from ..characteristics.string import StringCharacteristic from ..uuid import VendorUUID +from . import Service __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git" @@ -24,7 +24,7 @@ class CircuitPythonUUID(VendorUUID): """UUIDs with the CircuitPython base UUID.""" def __init__(self, uuid16: int) -> None: - uuid128 = bytearray("nhtyPtiucriC".encode("utf-8") + b"\x00\x00\xaf\xad") + uuid128 = bytearray(b"nhtyPtiucriC" + b"\x00\x00\xaf\xad") uuid128[-3] = uuid16 >> 8 uuid128[-4] = uuid16 & 0xFF super().__init__(uuid128) diff --git a/adafruit_ble/services/nordic.py b/adafruit_ble/services/nordic.py index 569ace9..cdbf0e0 100755 --- a/adafruit_ble/services/nordic.py +++ b/adafruit_ble/services/nordic.py @@ -13,16 +13,16 @@ from __future__ import annotations -from . import Service +from ..characteristics.stream import StreamIn, StreamOut from ..uuid import VendorUUID -from ..characteristics.stream import StreamOut, StreamIn +from . import Service try: - from typing import Optional, TYPE_CHECKING + from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from circuitpython_typing import WriteableBuffer, ReadableBuffer import _bleio + from circuitpython_typing import ReadableBuffer, WriteableBuffer except ImportError: pass @@ -38,17 +38,18 @@ class UARTService(Service): See ``examples/ble_uart_echo_test.py`` for a usage example. """ - # pylint: disable=no-member uuid = VendorUUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") _server_tx = StreamOut( uuid=VendorUUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"), timeout=1.0, - buffer_size=64, + # 512 is the largest negotiated MTU-3 value for all CircuitPython ports. + buffer_size=512, ) _server_rx = StreamIn( uuid=VendorUUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"), timeout=1.0, - buffer_size=64, + # 512 is the largest negotiated MTU-3 value for all CircuitPython ports. + buffer_size=512, ) def __init__(self, service: Optional[_bleio.Service] = None) -> None: @@ -62,6 +63,14 @@ def __init__(self, service: Optional[_bleio.Service] = None) -> None: self._tx = self._server_rx self._rx = self._server_tx + def deinit(self): + """The characteristic buffers must be deinitialized when no longer needed. + Otherwise they will leak storage. + """ + for obj in (self._tx, self._rx): + if hasattr(obj, "deinit"): + obj.deinit() + def read(self, nbytes: Optional[int] = None) -> Optional[bytes]: """ Read characters. If ``nbytes`` is specified then read at most that many bytes. @@ -73,9 +82,7 @@ def read(self, nbytes: Optional[int] = None) -> Optional[bytes]: """ return self._rx.read(nbytes) - def readinto( - self, buf: WriteableBuffer, nbytes: Optional[int] = None - ) -> Optional[int]: + def readinto(self, buf: WriteableBuffer, nbytes: Optional[int] = None) -> Optional[int]: """ Read bytes into the ``buf``. If ``nbytes`` is specified then read at most that many bytes. Otherwise, read at most ``len(buf)`` bytes. diff --git a/adafruit_ble/services/sphero.py b/adafruit_ble/services/sphero.py index 2c34d6f..0fa3337 100644 --- a/adafruit_ble/services/sphero.py +++ b/adafruit_ble/services/sphero.py @@ -10,8 +10,8 @@ """ -from . import Service from ..uuid import VendorUUID +from . import Service __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git" diff --git a/adafruit_ble/services/standard/__init__.py b/adafruit_ble/services/standard/__init__.py index 91fe0bb..f00ba56 100644 --- a/adafruit_ble/services/standard/__init__.py +++ b/adafruit_ble/services/standard/__init__.py @@ -12,12 +12,11 @@ import time -from .. import Service -from ...uuid import StandardUUID -from ...characteristics import Characteristic -from ...characteristics.string import StringCharacteristic -from ...characteristics import StructCharacteristic +from ...characteristics import Characteristic, StructCharacteristic from ...characteristics.int import Uint8Characteristic +from ...characteristics.string import StringCharacteristic +from ...uuid import StandardUUID +from .. import Service __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git" @@ -26,7 +25,6 @@ class AppearanceCharacteristic(StructCharacteristic): """What type of device it is""" - # pylint: disable=too-few-public-methods uuid = StandardUUID(0x2A01) def __init__(self, **kwargs) -> None: @@ -80,6 +78,4 @@ def struct_time(self) -> time.struct_time: """ year, month, day, hour, minute, second, weekday, _, _ = self.current_time # Bluetooth weekdays count from 1. struct_time counts from 0. - return time.struct_time( - (year, month, day, hour, minute, second, weekday - 1, -1, -1) - ) + return time.struct_time((year, month, day, hour, minute, second, weekday - 1, -1, -1)) diff --git a/adafruit_ble/services/standard/device_info.py b/adafruit_ble/services/standard/device_info.py index 0ebd8ac..65813cc 100644 --- a/adafruit_ble/services/standard/device_info.py +++ b/adafruit_ble/services/standard/device_info.py @@ -15,12 +15,13 @@ import os import sys -from .. import Service -from ...uuid import StandardUUID +from ...characteristics import StructCharacteristic from ...characteristics.string import FixedStringCharacteristic +from ...uuid import StandardUUID +from .. import Service try: - from typing import Optional, TYPE_CHECKING + from typing import TYPE_CHECKING, Iterable, Optional if TYPE_CHECKING: import _bleio @@ -42,6 +43,7 @@ class DeviceInfoService(Service): hardware_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2A27)) software_revision = FixedStringCharacteristic(uuid=StandardUUID(0x2A28)) manufacturer = FixedStringCharacteristic(uuid=StandardUUID(0x2A29)) + pnp_id = StructCharacteristic(" None: if not service: @@ -59,15 +62,17 @@ def __init__( model_number = sys.platform if serial_number is None: try: - import microcontroller # pylint: disable=import-outside-toplevel + import microcontroller - serial_number = binascii.hexlify( - microcontroller.cpu.uid # pylint: disable=no-member - ).decode("utf-8") + serial_number = binascii.hexlify(microcontroller.cpu.uid).decode("utf-8") except ImportError: pass if firmware_revision is None: firmware_revision = getattr(os.uname(), "version", None) + if pnp_id is None: + # These values are not necessarily valid according to the spec, + # but they work on Android and iOS. + pnp_id = (0x00, 0x0000, 0x0000, 0x0000) super().__init__( manufacturer=manufacturer, software_revision=software_revision, @@ -75,5 +80,6 @@ def __init__( serial_number=serial_number, firmware_revision=firmware_revision, hardware_revision=hardware_revision, + pnp_id=pnp_id, service=service, ) diff --git a/adafruit_ble/services/standard/hid.py b/adafruit_ble/services/standard/hid.py index 6409f21..03f4dcf 100755 --- a/adafruit_ble/services/standard/hid.py +++ b/adafruit_ble/services/standard/hid.py @@ -16,11 +16,10 @@ import struct -from micropython import const import _bleio +from micropython import const -from adafruit_ble.characteristics import Attribute -from adafruit_ble.characteristics import Characteristic +from adafruit_ble.characteristics import Attribute, Characteristic from adafruit_ble.characteristics.int import Uint8Characteristic from adafruit_ble.uuid import StandardUUID @@ -48,15 +47,14 @@ _APPEARANCE_HID_JOYSTICK = const(963) _APPEARANCE_HID_GAMEPAD = const(964) -# pylint: disable=line-too-long DEFAULT_HID_DESCRIPTOR = ( b"\x05\x01" # Usage Page (Generic Desktop Ctrls) b"\x09\x06" # Usage (Keyboard) - b"\xA1\x01" # Collection (Application) + b"\xa1\x01" # Collection (Application) b"\x85\x01" # Report ID (1) b"\x05\x07" # Usage Page (Kbrd/Keypad) - b"\x19\xE0" # Usage Minimum (\xE0) - b"\x29\xE7" # Usage Maximum (\xE7) + b"\x19\xe0" # Usage Minimum (\xE0) + b"\x29\xe7" # Usage Maximum (\xE7) b"\x15\x00" # Logical Minimum (0) b"\x25\x01" # Logical Maximum (1) b"\x75\x01" # Report Size (1) @@ -80,12 +78,12 @@ b"\x91\x02" # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) b"\x95\x03" # Report Count (3) b"\x91\x01" # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) - b"\xC0" # End Collection + b"\xc0" # End Collection b"\x05\x01" # Usage Page (Generic Desktop Ctrls) b"\x09\x02" # Usage (Mouse) - b"\xA1\x01" # Collection (Application) + b"\xa1\x01" # Collection (Application) b"\x09\x01" # Usage (Pointer) - b"\xA1\x00" # Collection (Physical) + b"\xa1\x00" # Collection (Physical) b"\x85\x02" # Report ID (2) b"\x05\x09" # Usage Page (Button) b"\x19\x01" # Usage Minimum (\x01) @@ -102,30 +100,30 @@ b"\x09\x30" # Usage (X) b"\x09\x31" # Usage (Y) b"\x15\x81" # Logical Minimum (-127) - b"\x25\x7F" # Logical Maximum (127) + b"\x25\x7f" # Logical Maximum (127) b"\x75\x08" # Report Size (8) b"\x95\x02" # Report Count (2) b"\x81\x06" # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) b"\x09\x38" # Usage (Wheel) b"\x15\x81" # Logical Minimum (-127) - b"\x25\x7F" # Logical Maximum (127) + b"\x25\x7f" # Logical Maximum (127) b"\x75\x08" # Report Size (8) b"\x95\x01" # Report Count (1) b"\x81\x06" # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) - b"\xC0" # End Collection - b"\xC0" # End Collection - b"\x05\x0C" # Usage Page (Consumer) + b"\xc0" # End Collection + b"\xc0" # End Collection + b"\x05\x0c" # Usage Page (Consumer) b"\x09\x01" # Usage (Consumer Control) - b"\xA1\x01" # Collection (Application) + b"\xa1\x01" # Collection (Application) b"\x85\x03" # Report ID (3) b"\x75\x10" # Report Size (16) b"\x95\x01" # Report Count (1) b"\x15\x01" # Logical Minimum (1) - b"\x26\x8C\x02" # Logical Maximum (652) + b"\x26\x8c\x02" # Logical Maximum (652) b"\x19\x01" # Usage Minimum (Consumer Control) - b"\x2A\x8C\x02" # Usage Maximum (AC Send) + b"\x2a\x8c\x02" # Usage Maximum (AC Send) b"\x81\x00" # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) - b"\xC0" # End Collection + b"\xc0" # End Collection # b'\x05\x01' # Usage Page (Generic Desktop Ctrls) # b'\x09\x05' # Usage (Game Pad) # b'\xA1\x01' # Collection (Application) @@ -151,7 +149,6 @@ # b'\xC0' # End Collection ) """Default HID descriptor: provides mouse, keyboard, and consumer control devices.""" -# pylint: enable=line-too-long # Boot keyboard and mouse not currently supported. @@ -211,7 +208,6 @@ def send_report(self, report: Dict) -> None: class ReportOut: """A single HID report that receives HID data from a client.""" - # pylint: disable=too-few-public-methods uuid = StandardUUID(_REPORT_UUID_NUM) def __init__( @@ -229,9 +225,7 @@ def __init__( max_length=max_length, fixed_length=True, properties=( - Characteristic.READ - | Characteristic.WRITE - | Characteristic.WRITE_NO_RESPONSE + Characteristic.READ | Characteristic.WRITE | Characteristic.WRITE_NO_RESPONSE ), read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.ENCRYPT_NO_MITM, @@ -292,11 +286,7 @@ class HIDService(Service): boot_keyboard_out = Characteristic( uuid=StandardUUID(0x2A32), - properties=( - Characteristic.READ - | Characteristic.WRITE - | Characteristic.WRITE_NO_RESPONSE - ), + properties=(Characteristic.READ | Characteristic.WRITE | Characteristic.WRITE_NO_RESPONSE), read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.ENCRYPT_NO_MITM, max_length=1, @@ -356,7 +346,6 @@ def __init__( self._init_devices() def _init_devices(self) -> None: - # pylint: disable=too-many-branches,too-many-statements,too-many-locals self.devices = [] hid_descriptor = self.report_map @@ -423,9 +412,7 @@ def get_report_info(collection: Dict, reports: Dict) -> None: if "type" in main: get_report_info(main, reports) else: - report_size, report_id, report_count = [ - x[0] for x in main["globals"][7:10] - ] + report_size, report_id, report_count = (x[0] for x in main["globals"][7:10]) if report_id not in reports: reports[report_id] = {"input_size": 0, "output_size": 0} if main["tag"] == "input": @@ -435,31 +422,32 @@ def get_report_info(collection: Dict, reports: Dict) -> None: for collection in top_level_collections: if collection["type"][0] != 1: - raise NotImplementedError( - "Only Application top level collections supported." - ) + raise NotImplementedError("Only Application top level collections supported.") usage_page = collection["globals"][0][0] usage = collection["locals"][0][0] reports = {} get_report_info(collection, reports) - if len(reports) > 1: - raise NotImplementedError( - "Only one report id per Application collection supported" - ) - - report_id, report = list(reports.items())[0] - output_size = report["output_size"] - if output_size > 0: - self.devices.append( - ReportOut( - self, report_id, usage_page, usage, max_length=output_size // 8 + for report_id, report in reports.items(): + output_size = report["output_size"] + if output_size > 0: + self.devices.append( + ReportOut( + self, + report_id, + usage_page, + usage, + max_length=output_size // 8, + ) ) - ) - input_size = reports[report_id]["input_size"] - if input_size > 0: - self.devices.append( - ReportIn( - self, report_id, usage_page, usage, max_length=input_size // 8 + input_size = report["input_size"] + if input_size > 0: + self.devices.append( + ReportIn( + self, + report_id, + usage_page, + usage, + max_length=input_size // 8, + ) ) - ) diff --git a/adafruit_ble/uuid/__init__.py b/adafruit_ble/uuid/__init__.py index 4459c6f..c0218cf 100644 --- a/adafruit_ble/uuid/__init__.py +++ b/adafruit_ble/uuid/__init__.py @@ -20,7 +20,6 @@ class UUID: """Top level UUID""" # TODO: Make subclassing _bleio.UUID work so we can just use it directly. - # pylint: disable=no-member def __hash__(self): return hash(self.bleio_uuid) diff --git a/docs/api.rst b/docs/api.rst index 20b685d..088eade 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,3 +1,6 @@ +API Reference +############# + :py:mod:`~adafruit_ble` ======================= diff --git a/docs/conf.py b/docs/conf.py index f19b877..7ddbfe4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- - # SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries # # SPDX-License-Identifier: MIT +import datetime import os import sys -import datetime sys.path.insert(0, os.path.abspath("..")) @@ -49,9 +47,7 @@ creation_year = "2019" current_year = str(datetime.datetime.now().year) year_duration = ( - current_year - if current_year == creation_year - else creation_year + " - " + current_year + current_year if current_year == creation_year else creation_year + " - " + current_year ) copyright = year_duration + " Dan Halbert for Adafruit Industries" author = "Dan Halbert" @@ -105,7 +101,6 @@ import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/examples/ble_advertising_simpletest.py b/examples/ble_advertising_simpletest.py index 2c2815e..7a98199 100644 --- a/examples/ble_advertising_simpletest.py +++ b/examples/ble_advertising_simpletest.py @@ -6,7 +6,6 @@ """ from adafruit_ble import BLERadio - from adafruit_ble.advertising import Advertisement ble = BLERadio() diff --git a/examples/ble_bluefruit_color_picker.py b/examples/ble_bluefruit_color_picker.py index 4facbae..4cf7fdc 100755 --- a/examples/ble_bluefruit_color_picker.py +++ b/examples/ble_bluefruit_color_picker.py @@ -5,9 +5,8 @@ import board import neopixel - -from adafruit_bluefruit_connect.packet import Packet from adafruit_bluefruit_connect.color_packet import ColorPacket +from adafruit_bluefruit_connect.packet import Packet from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement diff --git a/examples/ble_bluefruit_connect_plotter.py b/examples/ble_bluefruit_connect_plotter.py index 542c714..e737ea2 100755 --- a/examples/ble_bluefruit_connect_plotter.py +++ b/examples/ble_bluefruit_connect_plotter.py @@ -4,9 +4,11 @@ # CircuitPython Bluefruit LE Connect Plotter Example import time -import board -import analogio + import adafruit_thermistor +import analogio +import board + from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService @@ -34,5 +36,5 @@ def scale(value): while ble.connected: print((scale(light.value), thermistor.temperature)) - uart_server.write("{},{}\n".format(scale(light.value), thermistor.temperature)) + uart_server.write(f"{scale(light.value)},{thermistor.temperature}\n") time.sleep(0.1) diff --git a/examples/ble_color_proximity.py b/examples/ble_color_proximity.py index 31c56d3..7fa99e2 100644 --- a/examples/ble_color_proximity.py +++ b/examples/ble_color_proximity.py @@ -8,9 +8,9 @@ """ import time + import board import digitalio - import neopixel from adafruit_ble import BLERadio @@ -63,7 +63,7 @@ if last_i != i: color = color_options[i] neopixels.fill(color) - print("New color {:06x}".format(color)) + print(f"New color {color:06x}") advertisement.color = color ble.stop_advertising() ble.start_advertising(advertisement) diff --git a/examples/ble_current_time_service.py b/examples/ble_current_time_service.py index a62dfd0..6747c0c 100644 --- a/examples/ble_current_time_service.py +++ b/examples/ble_current_time_service.py @@ -7,6 +7,7 @@ """ import time + import adafruit_ble from adafruit_ble.advertising.standard import SolicitServicesAdvertisement from adafruit_ble.services.standard import CurrentTimeService diff --git a/examples/ble_demo_central.py b/examples/ble_demo_central.py index b7510ac..8ccda04 100644 --- a/examples/ble_demo_central.py +++ b/examples/ble_demo_central.py @@ -9,12 +9,11 @@ import time +import adafruit_lis3dh import board import busio import digitalio -import adafruit_lis3dh import neopixel - from adafruit_bluefruit_connect.color_packet import ColorPacket from adafruit_ble import BLERadio @@ -68,7 +67,7 @@ def scale(value): except OSError: try: uart_connection.disconnect() - except: # pylint: disable=bare-except + except: pass uart_connection = None time.sleep(0.3) diff --git a/examples/ble_demo_periph.py b/examples/ble_demo_periph.py index a9b5f92..bc45e7b 100644 --- a/examples/ble_demo_periph.py +++ b/examples/ble_demo_periph.py @@ -8,10 +8,10 @@ import board import neopixel +from adafruit_bluefruit_connect.color_packet import ColorPacket # Only the packet classes that are imported will be known to Packet. from adafruit_bluefruit_connect.packet import Packet -from adafruit_bluefruit_connect.color_packet import ColorPacket from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement diff --git a/examples/ble_detailed_scan.py b/examples/ble_detailed_scan.py index 7f5dfeb..cad9a3a 100644 --- a/examples/ble_detailed_scan.py +++ b/examples/ble_detailed_scan.py @@ -6,7 +6,6 @@ # specialty advertising types. from adafruit_ble import BLERadio - from adafruit_ble.advertising import Advertisement from adafruit_ble.advertising.standard import ProvideServicesAdvertisement diff --git a/examples/ble_device_info_service.py b/examples/ble_device_info_service.py index 34ac32b..32086c2 100644 --- a/examples/ble_device_info_service.py +++ b/examples/ble_device_info_service.py @@ -7,6 +7,7 @@ """ import time + import adafruit_ble from adafruit_ble.advertising.standard import Advertisement from adafruit_ble.services.standard.device_info import DeviceInfoService diff --git a/examples/ble_hid_periph.py b/examples/ble_hid_periph.py index 6b8053a..6224a95 100644 --- a/examples/ble_hid_periph.py +++ b/examples/ble_hid_periph.py @@ -15,9 +15,8 @@ import adafruit_ble from adafruit_ble.advertising import Advertisement from adafruit_ble.advertising.standard import ProvideServicesAdvertisement -from adafruit_ble.services.standard.hid import HIDService from adafruit_ble.services.standard.device_info import DeviceInfoService - +from adafruit_ble.services.standard.hid import HIDService # Use default HID descriptor hid = HIDService() diff --git a/examples/ble_json_central.py b/examples/ble_json_central.py index 654f7f4..557b183 100644 --- a/examples/ble_json_central.py +++ b/examples/ble_json_central.py @@ -5,10 +5,10 @@ # Read sensor readings from peripheral BLE device using a JSON characteristic. from ble_json_service import SensorService + from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement - ble = BLERadio() connection = None diff --git a/examples/ble_json_peripheral.py b/examples/ble_json_peripheral.py index e079a8a..14d13fb 100644 --- a/examples/ble_json_peripheral.py +++ b/examples/ble_json_peripheral.py @@ -4,13 +4,14 @@ # Provide readable sensor values and writable settings to connected devices via JSON characteristic. -import time import random +import time + from ble_json_service import SensorService + from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement - # Create BLE radio, custom service, and advertisement. ble = BLERadio() service = SensorService() diff --git a/examples/ble_json_service.py b/examples/ble_json_service.py index 6351564..eaf8ec1 100644 --- a/examples/ble_json_service.py +++ b/examples/ble_json_service.py @@ -4,10 +4,10 @@ # Read sensor readings from peripheral BLE device using a JSON characteristic. -from adafruit_ble.uuid import VendorUUID -from adafruit_ble.services import Service from adafruit_ble.characteristics import Characteristic from adafruit_ble.characteristics.json import JSONCharacteristic +from adafruit_ble.services import Service +from adafruit_ble.uuid import VendorUUID # A custom service with two JSON characteristics for this device. The "sensors" characteristic @@ -16,8 +16,6 @@ # service can be any valid random uuid (some BLE UUID's are reserved). # NOTE: JSON data is limited by characteristic max_length of 512 byes. class SensorService(Service): - # pylint: disable=too-few-public-methods - uuid = VendorUUID("51ad213f-e568-4e35-84e4-67af89c79ef0") settings = JSONCharacteristic( diff --git a/examples/ble_packet_buffer_service.py b/examples/ble_packet_buffer_service.py index 510e4f0..486ee3d 100644 --- a/examples/ble_packet_buffer_service.py +++ b/examples/ble_packet_buffer_service.py @@ -8,22 +8,20 @@ import _bleio -from adafruit_ble.services import Service from adafruit_ble.characteristics import ( Attribute, Characteristic, ComplexCharacteristic, ) +from adafruit_ble.services import Service from adafruit_ble.uuid import VendorUUID class PacketBufferUUID(VendorUUID): """UUIDs with the PacketBuffer base UUID.""" - # pylint: disable=too-few-public-methods - def __init__(self, uuid16): - uuid128 = bytearray("reffuBtekcaP".encode("utf-8") + b"\x00\x00\xaf\xad") + uuid128 = bytearray(b"reffuBtekcaP" + b"\x00\x00\xaf\xad") uuid128[-3] = uuid16 >> 8 uuid128[-4] = uuid16 & 0xFF super().__init__(uuid128) @@ -35,11 +33,9 @@ def __init__( *, uuid=None, buffer_size=4, - properties=Characteristic.WRITE_NO_RESPONSE - | Characteristic.NOTIFY - | Characteristic.READ, + properties=Characteristic.WRITE_NO_RESPONSE | Characteristic.NOTIFY | Characteristic.READ, read_perm=Attribute.OPEN, - write_perm=Attribute.OPEN + write_perm=Attribute.OPEN, ): self.buffer_size = buffer_size super().__init__( diff --git a/examples/ble_packet_buffer_test.py b/examples/ble_packet_buffer_test.py index 66c986f..3326ec5 100644 --- a/examples/ble_packet_buffer_test.py +++ b/examples/ble_packet_buffer_test.py @@ -11,7 +11,6 @@ from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement - ble = BLERadio() pbs = PacketBufferService() advertisement = ProvideServicesAdvertisement(pbs) diff --git a/examples/ble_uart_echo_client.py b/examples/ble_uart_echo_client.py index 48a0f23..28fc5ba 100644 --- a/examples/ble_uart_echo_client.py +++ b/examples/ble_uart_echo_client.py @@ -13,9 +13,7 @@ ble = BLERadio() while True: - while ble.connected and any( - UARTService in connection for connection in ble.connections - ): + while ble.connected and any(UARTService in connection for connection in ble.connections): for connection in ble.connections: if UARTService not in connection: continue diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..61f9966 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +target-version = "py38" +line-length = 100 + +[lint] +preview = true +select = ["I", "PL", "UP"] + +extend-select = [ + "D419", # empty-docstring + "E501", # line-too-long + "W291", # trailing-whitespace + "PLC0414", # useless-import-alias + "PLC2401", # non-ascii-name + "PLC2801", # unnecessary-dunder-call + "PLC3002", # unnecessary-direct-lambda-call + "E999", # syntax-error + "PLE0101", # return-in-init + "F706", # return-outside-function + "F704", # yield-outside-function + "PLE0116", # continue-in-finally + "PLE0117", # nonlocal-without-binding + "PLE0241", # duplicate-bases + "PLE0302", # unexpected-special-method-signature + "PLE0604", # invalid-all-object + "PLE0605", # invalid-all-format + "PLE0643", # potential-index-error + "PLE0704", # misplaced-bare-raise + "PLE1141", # dict-iter-missing-items + "PLE1142", # await-outside-async + "PLE1205", # logging-too-many-args + "PLE1206", # logging-too-few-args + "PLE1307", # bad-string-format-type + "PLE1310", # bad-str-strip-call + "PLE1507", # invalid-envvar-value + "PLE2502", # bidirectional-unicode + "PLE2510", # invalid-character-backspace + "PLE2512", # invalid-character-sub + "PLE2513", # invalid-character-esc + "PLE2514", # invalid-character-nul + "PLE2515", # invalid-character-zero-width-space + "PLR0124", # comparison-with-itself + "PLR0202", # no-classmethod-decorator + "PLR0203", # no-staticmethod-decorator + "UP004", # useless-object-inheritance + "PLR0206", # property-with-parameters + "PLR0904", # too-many-public-methods + "PLR0911", # too-many-return-statements + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "PLR0914", # too-many-locals + "PLR0915", # too-many-statements + "PLR0916", # too-many-boolean-expressions + "PLR1702", # too-many-nested-blocks + "PLR1704", # redefined-argument-from-local + "PLR1711", # useless-return + "C416", # unnecessary-comprehension + "PLR1733", # unnecessary-dict-index-lookup + "PLR1736", # unnecessary-list-index-lookup + + # ruff reports this rule is unstable + #"PLR6301", # no-self-use + + "PLW0108", # unnecessary-lambda + "PLW0120", # useless-else-on-loop + "PLW0127", # self-assigning-variable + "PLW0129", # assert-on-string-literal + "B033", # duplicate-value + "PLW0131", # named-expr-without-context + "PLW0245", # super-without-brackets + "PLW0406", # import-self + "PLW0602", # global-variable-not-assigned + "PLW0603", # global-statement + "PLW0604", # global-at-module-level + + # fails on the try: import typing used by libraries + #"F401", # unused-import + + "F841", # unused-variable + "E722", # bare-except + "PLW0711", # binary-op-exception + "PLW1501", # bad-open-mode + "PLW1508", # invalid-envvar-default + "PLW1509", # subprocess-popen-preexec-fn + "PLW2101", # useless-with-lock + "PLW3301", # nested-min-max +] + +ignore = [ + "PLR2004", # magic-value-comparison + "UP030", # format literals + "PLW1514", # unspecified-encoding + "PLR0913", # too-many-arguments + "PLR0915", # too-many-statements + "PLR0917", # too-many-positional-arguments + "PLR0904", # too-many-public-methods + "PLR0912", # too-many-branches + "PLR0916", # too-many-boolean-expressions + "PLR6301", # could-be-static no-self-use + "PLC0415", # import outside toplevel + "UP006", # built-in instead of typing import + "UP007", # use x | y for annatoations + "PLC2701", # private import + "PLW2901", # for loop var overwritten + "PLW1641", # object not implement hash + "PLR0911", # too many return + "E501", # line too long + "PLR0914", # too many local vars + "E722", # bare except +] + +[format] +line-ending = "lf" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy