├── tests ├── __init__.py └── test_init.py ├── src └── ld2410_ble │ ├── py.typed │ ├── exceptions.py │ ├── __init__.py │ ├── models.py │ ├── const.py │ └── ld2410_ble.py ├── docs ├── source │ ├── _static │ │ └── .gitkeep │ ├── changelog.md │ ├── contributing.md │ ├── usage.md │ ├── installation.md │ ├── index.md │ └── conf.py ├── Makefile └── make.bat ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1-bug_report.md │ └── 2-feature-request.md ├── workflows │ ├── hacktoberfest.yml │ ├── labels.yml │ ├── issue-manager.yml │ └── ci.yml └── labels.toml ├── .flake8 ├── renovate.json ├── .gitpod.yml ├── commitlint.config.js ├── setup.py ├── .all-contributorsrc ├── .idea ├── led-ble.iml ├── workspace.xml └── watcherTasks.xml ├── .editorconfig ├── .readthedocs.yml ├── CHANGELOG.md ├── examples └── run.py ├── .pre-commit-config.yaml ├── pyproject.toml ├── .gitignore ├── README.md ├── CONTRIBUTING.md ├── LICENSE └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ld2410_ble/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/_static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["930913"] 2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = docs 3 | max-line-length = 120 4 | -------------------------------------------------------------------------------- /docs/source/changelog.md: -------------------------------------------------------------------------------- 1 | ```{include} ../../CHANGELOG.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/source/contributing.md: -------------------------------------------------------------------------------- 1 | ```{include} ../../CONTRIBUTING.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>browniebroke/renovate-configs:python"] 3 | } 4 | -------------------------------------------------------------------------------- /tests/test_init.py: -------------------------------------------------------------------------------- 1 | import ld2410_ble 2 | 3 | 4 | def test_add(): 5 | assert ld2410_ble 6 | -------------------------------------------------------------------------------- /src/ld2410_ble/exceptions.py: -------------------------------------------------------------------------------- 1 | class CharacteristicMissingError(Exception): 2 | """Raised when a characteristic is missing.""" 3 | -------------------------------------------------------------------------------- /docs/source/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | To use this package, import it: 4 | 5 | ```python 6 | import ld2410_ble 7 | ``` 8 | 9 | TODO: Document usage 10 | -------------------------------------------------------------------------------- /docs/source/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | The package is published on [PyPI](https://pypi.org/project/deezer-python/) and can be installed with `pip` (or any equivalent): 4 | 5 | ```bash 6 | pip install ld2410-ble 7 | ``` 8 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - command: | 3 | pip install poetry 4 | PIP_USER=false poetry install 5 | - command: | 6 | pip install pre-commit 7 | pre-commit install 8 | PIP_USER=false pre-commit install-hooks 9 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "header-max-length": [0, "always", Infinity], 5 | "body-max-line-length": [0, "always", Infinity], 6 | "footer-max-line-length": [0, "always", Infinity], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This is a shim to allow GitHub to detect the package, build is done with poetry 4 | # Taken from https://github.com/Textualize/rich 5 | 6 | import setuptools 7 | 8 | if __name__ == "__main__": 9 | setuptools.setup(name="ld2410-ble") 10 | -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to LD2410 BLE documentation! 2 | 3 | ```{toctree} 4 | :caption: Installation & Usage 5 | :maxdepth: 2 6 | 7 | installation 8 | usage 9 | ``` 10 | 11 | ```{toctree} 12 | :caption: Project Info 13 | :maxdepth: 2 14 | 15 | changelog 16 | contributing 17 | ``` 18 | 19 | ```{include} ../../README.md 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 13 | **Additional context** 14 | Add any other context about the problem here. 15 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "ld2410-ble", 3 | "projectOwner": "930913", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 80, 10 | "commit": true, 11 | "commitConvention": "angular", 12 | "contributors": [], 13 | "contributorsPerLine": 7, 14 | "skipCi": true 15 | } 16 | -------------------------------------------------------------------------------- /.idea/led-ble.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /src/ld2410_ble/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | __version__ = "0.2.0" 4 | 5 | 6 | from bleak_retry_connector import get_device 7 | 8 | from .exceptions import CharacteristicMissingError 9 | from .ld2410_ble import BLEAK_EXCEPTIONS, LD2410BLE, LD2410BLEState 10 | 11 | __all__ = [ 12 | "BLEAK_EXCEPTIONS", 13 | "CharacteristicMissingError", 14 | "LD2410BLE", 15 | "LD2410BLEState", 16 | "get_device", 17 | ] 18 | -------------------------------------------------------------------------------- /.github/workflows/hacktoberfest.yml: -------------------------------------------------------------------------------- 1 | name: Hacktoberfest 2 | 3 | on: 4 | schedule: 5 | # Run every day in October 6 | - cron: "0 0 * 10 *" 7 | # Run on the 1st of November to revert 8 | - cron: "0 13 1 11 *" 9 | 10 | jobs: 11 | hacktoberfest: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: browniebroke/hacktoberfest-labeler-action@v2.2.0 16 | with: 17 | github_token: ${{ secrets.GH_PAT }} 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: enhancement 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Additional context** 14 | Add any other context or screenshots about the feature request here. 15 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync Github labels 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - ".github/**" 9 | 10 | jobs: 11 | labels: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python 16 | uses: actions/setup-python@v3 17 | with: 18 | python-version: 3.8 19 | - name: Install labels 20 | run: pip install labels 21 | - name: Sync config with Github 22 | run: labels -u ${{ github.repository_owner }} -t ${{ secrets.GITHUB_TOKEN }} sync -f .github/labels.toml 23 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Build documentation in the docs/ directory with Sphinx 8 | sphinx: 9 | configuration: docs/source/conf.py 10 | 11 | # Set the version of Python and other tools you might need 12 | build: 13 | os: ubuntu-20.04 14 | tools: 15 | python: "3.9" 16 | 17 | # Optionally declare the Python requirements required to build your docs 18 | python: 19 | install: 20 | - method: pip 21 | path: . 22 | extra_requirements: 23 | - docs 24 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /src/ld2410_ble/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass, field 4 | 5 | 6 | @dataclass(frozen=True) 7 | class LD2410BLEState: 8 | 9 | is_moving: bool = False 10 | is_static: bool = False 11 | moving_target_distance: int = 0 12 | moving_target_energy: int = 0 13 | static_target_distance: int = 0 14 | static_target_energy: int = 0 15 | detection_distance: int = 0 16 | max_motion_gates: int | None = 8 17 | max_static_gates: int | None = 8 18 | motion_energy_gates: list[int] | None = field( 19 | default_factory=lambda: [0, 0, 0, 0, 0, 0, 0, 0, 0] 20 | ) 21 | static_energy_gates: list[int] | None = field( 22 | default_factory=lambda: [0, 0, 0, 0, 0, 0, 0, 0, 0] 23 | ) 24 | -------------------------------------------------------------------------------- /.github/workflows/issue-manager.yml: -------------------------------------------------------------------------------- 1 | name: Issue Manager 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | issue_comment: 7 | types: 8 | - created 9 | issues: 10 | types: 11 | - labeled 12 | pull_request_target: 13 | types: 14 | - labeled 15 | workflow_dispatch: 16 | 17 | jobs: 18 | issue-manager: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: tiangolo/issue-manager@0.4.0 22 | with: 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | config: > 25 | { 26 | "answered": { 27 | "message": "Assuming the original issue was solved, it will be automatically closed now." 28 | }, 29 | "waiting": { 30 | "message": "Automatically closing. To re-open, please provide the additional information requested." 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## v0.2.0 (2023-02-02) 6 | ### Feature 7 | * Add configurable password ([`15ef013`](https://github.com/930913/ld2410-ble/commit/15ef013468befc5d74e8cda4f27b2cbee9c1fc76)) 8 | 9 | ### Fix 10 | * Bump isort build dependency ([`068d0a9`](https://github.com/930913/ld2410-ble/commit/068d0a93b01d89d9eb95df34b7ed8248921f8197)) 11 | 12 | ## v0.1.1 (2022-12-12) 13 | ### Fix 14 | * Replace namespace ([`f7ebc79`](https://github.com/930913/ld2410-ble/commit/f7ebc7938df71d4d06aae2a6de02da29a9e45b45)) 15 | 16 | ## v0.1.0 (2022-12-12) 17 | ### Feature 18 | * Handle disconnects ([`4ce126f`](https://github.com/930913/ld2410-ble/commit/4ce126f87d8b6029991d9faeef85b84c979a855e)) 19 | 20 | ## v0.0.1 (2022-12-07) 21 | ### Fix 22 | * Try change version ([`bd38389`](https://github.com/930913/ld2410-ble/commit/bd38389ffbe23ce3fbff09187a429db7e4b9f496)) 23 | * Wtf is this format ([`ec1fac6`](https://github.com/930913/ld2410-ble/commit/ec1fac63ed3541e14e07b7373eb9ccc9ff376e1b)) 24 | * Commit message ([`610be33`](https://github.com/930913/ld2410-ble/commit/610be33a12ebc791af0ce069dcc98686cf73087e)) 25 | 26 | ## v0.0.0 (2022-12-07) 27 | ### Feature 28 | Initial commit 29 | -------------------------------------------------------------------------------- /examples/run.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | 4 | from bleak import BleakScanner 5 | from bleak.backends.device import BLEDevice 6 | from bleak.backends.scanner import AdvertisementData 7 | 8 | from ld2410_ble import LD2410BLE, LD2410BLEState 9 | 10 | _LOGGER = logging.getLogger(__name__) 11 | 12 | ADDRESS = "D0291B39-3A1B-7FF2-787B-4E743FED5B25" 13 | ADDRESS = "D0291B39-3A1B-7FF2-787B-4E743FED5B25" 14 | 15 | 16 | async def run() -> None: 17 | scanner = BleakScanner() 18 | future: asyncio.Future[BLEDevice] = asyncio.Future() 19 | 20 | def on_detected(device: BLEDevice, adv: AdvertisementData) -> None: 21 | if future.done(): 22 | return 23 | _LOGGER.info("Detected: %s", device) 24 | if device.address.lower() == ADDRESS.lower(): 25 | _LOGGER.info("Found device: %s", device.address) 26 | future.set_result(device) 27 | 28 | scanner.register_detection_callback(on_detected) 29 | await scanner.start() 30 | 31 | def on_state_changed(state: LD2410BLEState) -> None: 32 | _LOGGER.info("State changed: %s", state) 33 | 34 | device = await future 35 | ld2410b = LD2410BLE(device) 36 | cancel_callback = ld2410b.register_callback(on_state_changed) 37 | await ld2410b.initialise() 38 | await asyncio.sleep(10) 39 | cancel_callback() 40 | await scanner.stop() 41 | 42 | 43 | logging.basicConfig(level=logging.INFO) 44 | logging.getLogger("ld2410_ble").setLevel(logging.DEBUG) 45 | asyncio.run(run()) 46 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | exclude: "CHANGELOG.md" 4 | default_stages: [commit] 5 | 6 | ci: 7 | autofix_commit_msg: "chore(pre-commit.ci): auto fixes" 8 | autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate" 9 | 10 | repos: 11 | - repo: https://github.com/commitizen-tools/commitizen 12 | rev: v2.29.1 13 | hooks: 14 | - id: commitizen 15 | stages: [commit-msg] 16 | - repo: https://github.com/pre-commit/pre-commit-hooks 17 | rev: v4.3.0 18 | hooks: 19 | - id: debug-statements 20 | - id: check-builtin-literals 21 | - id: check-case-conflict 22 | - id: check-docstring-first 23 | - id: check-json 24 | - id: check-toml 25 | - id: check-xml 26 | - id: check-yaml 27 | - id: detect-private-key 28 | - id: end-of-file-fixer 29 | - id: trailing-whitespace 30 | - id: debug-statements 31 | - repo: https://github.com/pre-commit/mirrors-prettier 32 | rev: v2.7.1 33 | hooks: 34 | - id: prettier 35 | args: ["--tab-width", "2"] 36 | - repo: https://github.com/asottile/pyupgrade 37 | rev: v2.37.3 38 | hooks: 39 | - id: pyupgrade 40 | args: [--py37-plus] 41 | - repo: https://github.com/PyCQA/isort 42 | rev: 5.12.0 43 | hooks: 44 | - id: isort 45 | - repo: https://github.com/psf/black 46 | rev: 22.6.0 47 | hooks: 48 | - id: black 49 | - repo: https://github.com/codespell-project/codespell 50 | rev: v2.1.0 51 | hooks: 52 | - id: codespell 53 | - repo: https://github.com/PyCQA/flake8 54 | rev: 4.0.1 55 | hooks: 56 | - id: flake8 57 | - repo: https://github.com/pre-commit/mirrors-mypy 58 | rev: v0.931 59 | hooks: 60 | - id: mypy 61 | additional_dependencies: [] 62 | - repo: https://github.com/PyCQA/bandit 63 | rev: 1.7.4 64 | hooks: 65 | - id: bandit 66 | args: [-x, tests] 67 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | from typing import Any, List 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = "LD2410 BLE" 21 | copyright = "2023, 930913" 22 | author = "930913" 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | "myst_parser", 32 | ] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = [".rst", ".md"] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns: List[Any] = [] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = "sphinx_rtd_theme" 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ["_static"] 57 | -------------------------------------------------------------------------------- /src/ld2410_ble/const.py: -------------------------------------------------------------------------------- 1 | CHARACTERISTIC_NOTIFY = "0000fff1-0000-1000-8000-00805f9b34fb" 2 | CHARACTERISTIC_WRITE = "0000fff2-0000-1000-8000-00805f9b34fb" 3 | 4 | CMD_BT_PASS_PRE = b"\xfd\xfc\xfb\xfa\x08\x00\xa8\x00" 5 | CMD_BT_PASS_DEFAULT = b"HiLink" 6 | CMD_BT_PASS_POST = b"\x04\x03\x02\x01" 7 | CMD_ENABLE_CONFIG = b"\xfd\xfc\xfb\xfa\x04\x00\xff\x00\x01\x00\x04\x03\x02\x01" 8 | CMD_ENABLE_ENGINEERING_MODE = b"\xfd\xfc\xfb\xfa\x02\x00b\x00\x04\x03\x02\x01" 9 | CMD_DISABLE_CONFIG = b"\xfd\xfc\xfb\xfa\x02\x00\xfe\x00\x04\x03\x02\x01" 10 | 11 | MOVING_TARGET = 1 12 | STATIC_TARGET = 2 13 | 14 | frame_start = b"\xf4\xf3\xf2\xf1" 15 | frame_length = b"(?P..)" 16 | frame_engineering_mode = b"(?P\x01|\x02)" 17 | frame_head = b"\xaa" 18 | frame_target_state = b"(?P\x00|\x01|\x02|\x03)" 19 | frame_moving_target_distance = b"(?P..)" 20 | frame_moving_target_energy = b"(?P.)" 21 | frame_static_target_distance = b"(?P..)" 22 | frame_static_target_energy = b"(?P.)" 23 | frame_detection_distance = b"(?P..)" 24 | frame_engineering_data = b"(?P.+?)?" 25 | frame_tail = b"\x55" 26 | frame_check = b"\x00" 27 | frame_end = b"\xf8\xf7\xf6\xf5" 28 | 29 | frame_maximum_motion_gates = b"(?P.)" 30 | frame_maximum_static_gates = b"(?P.)" 31 | frame_motion_energy_gates = b"(?P.{9})" 32 | frame_static_energy_gates = b"(?P.{9})" 33 | frame_additional_information = b"(?P.*)" 34 | 35 | frame_regex = ( 36 | frame_start 37 | + frame_length 38 | + frame_engineering_mode 39 | + frame_head 40 | + frame_target_state 41 | + frame_moving_target_distance 42 | + frame_moving_target_energy 43 | + frame_static_target_distance 44 | + frame_static_target_energy 45 | + frame_detection_distance 46 | + frame_engineering_data 47 | + frame_tail 48 | + frame_check 49 | + frame_end 50 | ) 51 | 52 | engineering_frame_regex = ( 53 | frame_maximum_motion_gates 54 | + frame_maximum_static_gates 55 | + frame_motion_energy_gates 56 | + frame_static_energy_gates 57 | + frame_additional_information 58 | ) 59 | -------------------------------------------------------------------------------- /.github/labels.toml: -------------------------------------------------------------------------------- 1 | [breaking] 2 | color = "ffcc00" 3 | name = "breaking" 4 | description = "Breaking change." 5 | 6 | [bug] 7 | color = "d73a4a" 8 | name = "bug" 9 | description = "Something isn't working" 10 | 11 | [dependencies] 12 | color = "0366d6" 13 | name = "dependencies" 14 | description = "Pull requests that update a dependency file" 15 | 16 | [github_actions] 17 | color = "000000" 18 | name = "github_actions" 19 | description = "Update of github actions" 20 | 21 | [documentation] 22 | color = "1bc4a5" 23 | name = "documentation" 24 | description = "Improvements or additions to documentation" 25 | 26 | [duplicate] 27 | color = "cfd3d7" 28 | name = "duplicate" 29 | description = "This issue or pull request already exists" 30 | 31 | [enhancement] 32 | color = "a2eeef" 33 | name = "enhancement" 34 | description = "New feature or request" 35 | 36 | ["good first issue"] 37 | color = "7057ff" 38 | name = "good first issue" 39 | description = "Good for newcomers" 40 | 41 | ["help wanted"] 42 | color = "008672" 43 | name = "help wanted" 44 | description = "Extra attention is needed" 45 | 46 | [invalid] 47 | color = "e4e669" 48 | name = "invalid" 49 | description = "This doesn't seem right" 50 | 51 | [nochangelog] 52 | color = "555555" 53 | name = "nochangelog" 54 | description = "Exclude pull requests from changelog" 55 | 56 | [question] 57 | color = "d876e3" 58 | name = "question" 59 | description = "Further information is requested" 60 | 61 | [removed] 62 | color = "e99695" 63 | name = "removed" 64 | description = "Removed piece of functionalities." 65 | 66 | [tests] 67 | color = "bfd4f2" 68 | name = "tests" 69 | description = "CI, CD and testing related changes" 70 | 71 | [wontfix] 72 | color = "ffffff" 73 | name = "wontfix" 74 | description = "This will not be worked on" 75 | 76 | [discussion] 77 | color = "c2e0c6" 78 | name = "discussion" 79 | description = "Some discussion around the project" 80 | 81 | [hacktoberfest] 82 | color = "ffa663" 83 | name = "hacktoberfest" 84 | description = "Good issues for Hacktoberfest" 85 | 86 | [answered] 87 | color = "0ee2b6" 88 | name = "answered" 89 | description = "Automatically closes as answered after a delay" 90 | 91 | [waiting] 92 | color = "5f7972" 93 | name = "waiting" 94 | description = "Automatically closes if no answer after a delay" 95 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | concurrency: 10 | group: ${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-python@v3 19 | with: 20 | python-version: "3.9" 21 | - uses: pre-commit/action@v2.0.3 22 | 23 | # Make sure commit messages follow the conventional commits convention: 24 | # https://www.conventionalcommits.org 25 | commitlint: 26 | name: Lint Commit Messages 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | with: 31 | fetch-depth: 0 32 | - uses: wagoid/commitlint-github-action@v4.1.11 33 | 34 | test: 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | python-version: 39 | - "3.9" 40 | - "3.10" 41 | os: 42 | - ubuntu-latest 43 | runs-on: ${{ matrix.os }} 44 | steps: 45 | - uses: actions/checkout@v3 46 | - name: Set up Python 47 | uses: actions/setup-python@v3 48 | with: 49 | python-version: ${{ matrix.python-version }} 50 | - uses: snok/install-poetry@v1 51 | - name: Install Dependencies 52 | run: poetry install 53 | - name: Test with Pytest 54 | run: poetry run pytest --cov-report=xml 55 | - name: Upload coverage to Codecov 56 | uses: codecov/codecov-action@v3 57 | with: 58 | token: ${{ secrets.CODECOV_TOKEN }} 59 | 60 | release: 61 | runs-on: ubuntu-latest 62 | environment: release 63 | if: github.ref == 'refs/heads/main' 64 | needs: 65 | - test 66 | - lint 67 | - commitlint 68 | 69 | steps: 70 | - uses: actions/checkout@v3 71 | with: 72 | fetch-depth: 0 73 | 74 | # Run semantic release: 75 | # - Update CHANGELOG.md 76 | # - Update version in code 77 | # - Create git tag 78 | # - Create GitHub release 79 | # - Publish to PyPI 80 | - name: Python Semantic Release 81 | uses: relekang/python-semantic-release@v7.31.4 82 | with: 83 | github_token: ${{ secrets.GITHUB_TOKEN }} 84 | pypi_token: ${{ secrets.PYPI_TOKEN }} 85 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "ld2410-ble" 3 | version = "0.2.0" 4 | description = "Interface with LD2410B modules from HiLink" 5 | authors = ["930913 <3722064+930913@users.noreply.github.com>"] 6 | license = "Apache Software License 2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/930913/ld2410-ble" 9 | documentation = "https://ld2410-ble.readthedocs.io" 10 | classifiers = [ 11 | "Development Status :: 2 - Pre-Alpha", 12 | "Intended Audience :: Developers", 13 | "Natural Language :: English", 14 | "Operating System :: OS Independent", 15 | "Topic :: Software Development :: Libraries", 16 | ] 17 | packages = [ 18 | { include = "ld2410_ble", from = "src" }, 19 | ] 20 | 21 | [tool.poetry.urls] 22 | "Bug Tracker" = "https://github.com/930913/ld2410-ble/issues" 23 | "Changelog" = "https://github.com/930913/ld2410-ble/blob/main/CHANGELOG.md" 24 | 25 | [tool.poetry.dependencies] 26 | python = "^3.9" 27 | 28 | # Documentation Dependencies 29 | Sphinx = {version = "^5.0", optional = true} 30 | sphinx-rtd-theme = {version = "^1.0", optional = true} 31 | myst-parser = {version = "^0.18", optional = true} 32 | bleak-retry-connector = ">=2.3.0" 33 | bleak = ">=0.19.0" 34 | async-timeout = ">=4.0.1" 35 | 36 | [tool.poetry.extras] 37 | docs = [ 38 | "myst-parser", 39 | "sphinx", 40 | "sphinx-rtd-theme", 41 | ] 42 | 43 | [tool.poetry.dev-dependencies] 44 | pytest = "^7.0" 45 | pytest-cov = "^3.0" 46 | 47 | [tool.semantic_release] 48 | branch = "main" 49 | version_toml = "pyproject.toml:tool.poetry.version" 50 | version_variable = "src/ld2410_ble/__init__.py:__version__" 51 | build_command = "pip install poetry && poetry build" 52 | 53 | [tool.pytest.ini_options] 54 | addopts = "-v -Wdefault --cov=ld2410_ble --cov-report=term-missing:skip-covered" 55 | pythonpath = ["src"] 56 | 57 | [tool.coverage.run] 58 | branch = true 59 | 60 | [tool.coverage.report] 61 | exclude_lines = [ 62 | "pragma: no cover", 63 | "@overload", 64 | "if TYPE_CHECKING", 65 | "raise NotImplementedError", 66 | ] 67 | 68 | [tool.isort] 69 | profile = "black" 70 | known_first_party = ["ld2410_ble", "tests"] 71 | 72 | [tool.mypy] 73 | check_untyped_defs = true 74 | disallow_any_generics = true 75 | disallow_incomplete_defs = true 76 | disallow_untyped_defs = true 77 | mypy_path = "src/" 78 | no_implicit_optional = true 79 | show_error_codes = true 80 | warn_unreachable = true 81 | warn_unused_ignores = true 82 | exclude = [ 83 | 'docs/.*', 84 | 'setup.py', 85 | ] 86 | 87 | [[tool.mypy.overrides]] 88 | module = "tests.*" 89 | allow_untyped_defs = true 90 | 91 | [[tool.mypy.overrides]] 92 | module = "docs.*" 93 | ignore_errors = true 94 | 95 | [build-system] 96 | requires = ["poetry-core>=1.0.0"] 97 | build-backend = "poetry.core.masonry.api" 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ 141 | -------------------------------------------------------------------------------- /.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 24 | 25 | 36 | 44 | 45 | 56 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LD2410 BLE 2 | 3 |

4 | 5 | CI Status 6 | 7 | 8 | Documentation Status 9 | 10 | 11 | Test coverage percentage 12 | 13 |

14 |

15 | 16 | Poetry 17 | 18 | 19 | black 20 | 21 | 22 | pre-commit 23 | 24 |

25 |

26 | 27 | PyPI Version 28 | 29 | Supported Python versions 30 | License 31 |

32 | 33 | Interface with LD2410B modules from HiLink 34 | 35 | ## Installation 36 | 37 | Install this via pip (or your favourite package manager): 38 | 39 | `pip install ld2410-ble` 40 | 41 | ## Contributors ✨ 42 | 43 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 53 | 54 | ## Credits 55 | 56 | This package was created with 57 | [Cookiecutter](https://github.com/audreyr/cookiecutter) and the 58 | [browniebroke/cookiecutter-pypackage](https://github.com/browniebroke/cookiecutter-pypackage) 59 | project template. 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given. 4 | 5 | You can contribute in many ways: 6 | 7 | ## Types of Contributions 8 | 9 | ### Report Bugs 10 | 11 | Report bugs to [our issue page][gh-issues]. If you are reporting a bug, please include: 12 | 13 | - Your operating system name and version. 14 | - Any details about your local setup that might be helpful in troubleshooting. 15 | - Detailed steps to reproduce the bug. 16 | 17 | ### Fix Bugs 18 | 19 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. 20 | 21 | ### Implement Features 22 | 23 | Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. 24 | 25 | ### Write Documentation 26 | 27 | LD2410 BLE could always use more documentation, whether as part of the official LD2410 BLE docs, in docstrings, or even on the web in blog posts, articles, and such. 28 | 29 | ### Submit Feedback 30 | 31 | The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature: 32 | 33 | - Explain in detail how it would work. 34 | - Keep the scope as narrow as possible, to make it easier to implement. 35 | - Remember that this is a volunteer-driven project, and that contributions are welcome 😊 36 | 37 | ## Get Started! 38 | 39 | Ready to contribute? Here's how to set yourself up for local development. 40 | 41 | 1. Fork the repo on GitHub. 42 | 43 | 2. Clone your fork locally: 44 | 45 | ```shell 46 | $ git clone git@github.com:/ld2410-ble.git 47 | ``` 48 | 49 | 3. Install the project dependencies with [Poetry](https://python-poetry.org): 50 | 51 | ```shell 52 | $ poetry install 53 | ``` 54 | 55 | 4. Create a branch for local development: 56 | 57 | ```shell 58 | $ git checkout -b name-of-your-bugfix-or-feature 59 | ``` 60 | 61 | Now you can make your changes locally. 62 | 63 | 5. When you're done making changes, check that your changes pass our tests: 64 | 65 | ```shell 66 | $ poetry run pytest 67 | ``` 68 | 69 | 6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off: 70 | 71 | ```shell 72 | $ pre-commit run -a 73 | ``` 74 | 75 | Or better, install the hooks once and have them run automatically each time you commit: 76 | 77 | ```shell 78 | $ pre-commit install 79 | ``` 80 | 81 | 7. Commit your changes and push your branch to GitHub: 82 | 83 | ```shell 84 | $ git add . 85 | $ git commit -m "feat(something): your detailed description of your changes" 86 | $ git push origin name-of-your-bugfix-or-feature 87 | ``` 88 | 89 | Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time. 90 | 91 | 8. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed): 92 | 93 | ```shell 94 | $ gh pr create --fill 95 | ``` 96 | 97 | ## Pull Request Guidelines 98 | 99 | We like to have the pull request open as soon as possible, that's a great place to discuss any piece of work, even unfinished. You can use draft pull request if it's still a work in progress. Here are a few guidelines to follow: 100 | 101 | 1. Include tests for feature or bug fixes. 102 | 2. Update the documentation for significant features. 103 | 3. Ensure tests are passing on CI. 104 | 105 | ## Tips 106 | 107 | To run a subset of tests: 108 | 109 | ```shell 110 | $ pytest tests 111 | ``` 112 | 113 | ## Making a new release 114 | 115 | The deployment should be automated and can be triggered from the Semantic Release workflow in GitHub. The next version will be based on [the commit logs](https://python-semantic-release.readthedocs.io/en/latest/commit-log-parsing.html#commit-log-parsing). This is done by [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/index.html) via a GitHub action. 116 | 117 | [gh-issues]: https://github.com/930913/ld2410-ble/issues 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2022 930913 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/ld2410_ble/ld2410_ble.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import logging 5 | import re 6 | import sys 7 | from collections.abc import Callable 8 | from typing import Any, TypeVar 9 | 10 | from bleak.backends.device import BLEDevice 11 | from bleak.backends.scanner import AdvertisementData 12 | from bleak.exc import BleakDBusError 13 | from bleak_retry_connector import BLEAK_RETRY_EXCEPTIONS as BLEAK_EXCEPTIONS 14 | from bleak_retry_connector import ( 15 | BleakClientWithServiceCache, 16 | BleakError, 17 | BleakNotFoundError, 18 | establish_connection, 19 | retry_bluetooth_connection_error, 20 | ) 21 | 22 | from .const import ( 23 | CHARACTERISTIC_NOTIFY, 24 | CHARACTERISTIC_WRITE, 25 | CMD_BT_PASS_DEFAULT, 26 | CMD_BT_PASS_POST, 27 | CMD_BT_PASS_PRE, 28 | CMD_DISABLE_CONFIG, 29 | CMD_ENABLE_CONFIG, 30 | CMD_ENABLE_ENGINEERING_MODE, 31 | MOVING_TARGET, 32 | STATIC_TARGET, 33 | engineering_frame_regex, 34 | frame_regex, 35 | ) 36 | from .exceptions import CharacteristicMissingError 37 | from .models import LD2410BLEState 38 | 39 | BLEAK_BACKOFF_TIME = 0.25 40 | 41 | __version__ = "0.0.0" 42 | 43 | 44 | WrapFuncType = TypeVar("WrapFuncType", bound=Callable[..., Any]) 45 | 46 | RETRY_BACKOFF_EXCEPTIONS = (BleakDBusError,) 47 | 48 | _LOGGER = logging.getLogger(__name__) 49 | 50 | DEFAULT_ATTEMPTS = sys.maxsize 51 | 52 | 53 | class LD2410BLE: 54 | def __init__( 55 | self, 56 | ble_device: BLEDevice, 57 | advertisement_data: AdvertisementData | None = None, 58 | password: bytes = CMD_BT_PASS_DEFAULT, 59 | ) -> None: 60 | """Init the LD2410BLE.""" 61 | self._ble_device = ble_device 62 | self._advertisement_data = advertisement_data 63 | self._password = password 64 | self._operation_lock = asyncio.Lock() 65 | self._state = LD2410BLEState() 66 | self._connect_lock: asyncio.Lock = asyncio.Lock() 67 | self._client: BleakClientWithServiceCache | None = None 68 | self._expected_disconnect = False 69 | self.loop = asyncio.get_running_loop() 70 | self._callbacks: list[Callable[[LD2410BLEState], None]] = [] 71 | self._disconnected_callbacks: list[Callable[[], None]] = [] 72 | self._buf = b"" 73 | 74 | def set_ble_device_and_advertisement_data( 75 | self, ble_device: BLEDevice, advertisement_data: AdvertisementData 76 | ) -> None: 77 | """Set the ble device.""" 78 | self._ble_device = ble_device 79 | self._advertisement_data = advertisement_data 80 | 81 | @property 82 | def address(self) -> str: 83 | """Return the address.""" 84 | return self._ble_device.address 85 | 86 | @property 87 | def _address(self) -> str: 88 | """Return the address.""" 89 | return self._ble_device.address 90 | 91 | @property 92 | def name(self) -> str: 93 | """Get the name of the device.""" 94 | return self._ble_device.name or self._ble_device.address 95 | 96 | @property 97 | def rssi(self) -> int | None: 98 | """Get the rssi of the device.""" 99 | if self._advertisement_data: 100 | return self._advertisement_data.rssi 101 | return None 102 | 103 | @property 104 | def state(self) -> LD2410BLEState: 105 | """Return the state.""" 106 | return self._state 107 | 108 | @property 109 | def is_moving(self) -> bool: 110 | return self._state.is_moving 111 | 112 | @property 113 | def is_static(self) -> bool: 114 | return self._state.is_static 115 | 116 | @property 117 | def moving_target_distance(self) -> int: 118 | return self._state.moving_target_distance 119 | 120 | @property 121 | def moving_target_energy(self) -> int: 122 | return self._state.moving_target_energy 123 | 124 | @property 125 | def static_target_distance(self) -> int: 126 | return self._state.static_target_distance 127 | 128 | @property 129 | def static_target_energy(self) -> int: 130 | return self._state.static_target_energy 131 | 132 | @property 133 | def detection_distance(self) -> int: 134 | return self._state.detection_distance 135 | 136 | @property 137 | def max_motion_gates(self) -> int: 138 | assert self._state.max_motion_gates is not None # nosec 139 | return self._state.max_motion_gates 140 | 141 | @property 142 | def max_static_gates(self) -> int: 143 | assert self._state.max_static_gates is not None # nosec 144 | return self._state.max_static_gates 145 | 146 | @property 147 | def motion_energy_gates(self) -> list[int]: 148 | assert self._state.motion_energy_gates is not None # nosec 149 | return self._state.motion_energy_gates 150 | 151 | @property 152 | def motion_energy_gate_0(self) -> int: 153 | assert self._state.motion_energy_gates is not None # nosec 154 | return self._state.motion_energy_gates[0] 155 | 156 | @property 157 | def motion_energy_gate_1(self) -> int: 158 | assert self._state.motion_energy_gates is not None # nosec 159 | return self._state.motion_energy_gates[1] 160 | 161 | @property 162 | def motion_energy_gate_2(self) -> int: 163 | assert self._state.motion_energy_gates is not None # nosec 164 | return self._state.motion_energy_gates[2] 165 | 166 | @property 167 | def motion_energy_gate_3(self) -> int: 168 | assert self._state.motion_energy_gates is not None # nosec 169 | return self._state.motion_energy_gates[3] 170 | 171 | @property 172 | def motion_energy_gate_4(self) -> int: 173 | assert self._state.motion_energy_gates is not None # nosec 174 | return self._state.motion_energy_gates[4] 175 | 176 | @property 177 | def motion_energy_gate_5(self) -> int: 178 | assert self._state.motion_energy_gates is not None # nosec 179 | return self._state.motion_energy_gates[5] 180 | 181 | @property 182 | def motion_energy_gate_6(self) -> int: 183 | assert self._state.motion_energy_gates is not None # nosec 184 | return self._state.motion_energy_gates[6] 185 | 186 | @property 187 | def motion_energy_gate_7(self) -> int: 188 | assert self._state.motion_energy_gates is not None # nosec 189 | return self._state.motion_energy_gates[7] 190 | 191 | @property 192 | def motion_energy_gate_8(self) -> int: 193 | assert self._state.motion_energy_gates is not None # nosec 194 | return self._state.motion_energy_gates[8] 195 | 196 | @property 197 | def static_energy_gates(self) -> list[int]: 198 | assert self._state.static_energy_gates is not None # nosec 199 | return self._state.static_energy_gates 200 | 201 | @property 202 | def static_energy_gate_0(self) -> int: 203 | assert self._state.static_energy_gates is not None # nosec 204 | return self._state.static_energy_gates[0] 205 | 206 | @property 207 | def static_energy_gate_1(self) -> int: 208 | assert self._state.static_energy_gates is not None # nosec 209 | return self._state.static_energy_gates[1] 210 | 211 | @property 212 | def static_energy_gate_2(self) -> int: 213 | assert self._state.static_energy_gates is not None # nosec 214 | return self._state.static_energy_gates[2] 215 | 216 | @property 217 | def static_energy_gate_3(self) -> int: 218 | assert self._state.static_energy_gates is not None # nosec 219 | return self._state.static_energy_gates[3] 220 | 221 | @property 222 | def static_energy_gate_4(self) -> int: 223 | assert self._state.static_energy_gates is not None # nosec 224 | return self._state.static_energy_gates[4] 225 | 226 | @property 227 | def static_energy_gate_5(self) -> int: 228 | assert self._state.static_energy_gates is not None # nosec 229 | return self._state.static_energy_gates[5] 230 | 231 | @property 232 | def static_energy_gate_6(self) -> int: 233 | assert self._state.static_energy_gates is not None # nosec 234 | return self._state.static_energy_gates[6] 235 | 236 | @property 237 | def static_energy_gate_7(self) -> int: 238 | assert self._state.static_energy_gates is not None # nosec 239 | return self._state.static_energy_gates[7] 240 | 241 | @property 242 | def static_energy_gate_8(self) -> int: 243 | assert self._state.static_energy_gates is not None # nosec 244 | return self._state.static_energy_gates[8] 245 | 246 | async def stop(self) -> None: 247 | """Stop the LD2410BLE.""" 248 | _LOGGER.debug("%s: Stop", self.name) 249 | await self._execute_disconnect() 250 | 251 | def _fire_callbacks(self) -> None: 252 | """Fire the callbacks.""" 253 | for callback in self._callbacks: 254 | callback(self._state) 255 | 256 | def register_callback( 257 | self, callback: Callable[[LD2410BLEState], None] 258 | ) -> Callable[[], None]: 259 | """Register a callback to be called when the state changes.""" 260 | 261 | def unregister_callback() -> None: 262 | self._callbacks.remove(callback) 263 | 264 | self._callbacks.append(callback) 265 | return unregister_callback 266 | 267 | def _fire_disconnected_callbacks(self) -> None: 268 | """Fire the callbacks.""" 269 | for callback in self._disconnected_callbacks: 270 | callback() 271 | 272 | def register_disconnected_callback( 273 | self, callback: Callable[[], None] 274 | ) -> Callable[[], None]: 275 | """Register a callback to be called when the state changes.""" 276 | 277 | def unregister_callback() -> None: 278 | self._disconnected_callbacks.remove(callback) 279 | 280 | self._disconnected_callbacks.append(callback) 281 | return unregister_callback 282 | 283 | async def initialise(self) -> None: 284 | _LOGGER.debug("%s: Sending configuration commands", self.name) 285 | await self._send_command(CMD_BT_PASS_PRE + self._password + CMD_BT_PASS_POST) 286 | await asyncio.sleep(0.1) 287 | await self._send_command(CMD_ENABLE_CONFIG) 288 | await asyncio.sleep(0.1) 289 | await self._send_command(CMD_ENABLE_ENGINEERING_MODE) 290 | await asyncio.sleep(0.1) 291 | await self._send_command(CMD_DISABLE_CONFIG) 292 | await asyncio.sleep(0.1) 293 | 294 | _LOGGER.debug("%s: Subscribe to notifications; RSSI: %s", self.name, self.rssi) 295 | if self._client is not None: 296 | await self._client.start_notify( 297 | CHARACTERISTIC_NOTIFY, self._notification_handler 298 | ) 299 | 300 | async def _ensure_connected(self) -> None: 301 | """Ensure connection to device is established.""" 302 | if self._connect_lock.locked(): 303 | _LOGGER.debug( 304 | "%s: Connection already in progress, waiting for it to complete; RSSI: %s", 305 | self.name, 306 | self.rssi, 307 | ) 308 | if self._client and self._client.is_connected: 309 | return 310 | async with self._connect_lock: 311 | # Check again while holding the lock 312 | if self._client and self._client.is_connected: 313 | return 314 | _LOGGER.debug("%s: Connecting; RSSI: %s", self.name, self.rssi) 315 | client = await establish_connection( 316 | BleakClientWithServiceCache, 317 | self._ble_device, 318 | self.name, 319 | self._disconnected, 320 | use_services_cache=True, 321 | ble_device_callback=lambda: self._ble_device, 322 | ) 323 | _LOGGER.debug("%s: Connected; RSSI: %s", self.name, self.rssi) 324 | 325 | self._client = client 326 | 327 | async def _reconnect(self) -> None: 328 | """Attempt a reconnect""" 329 | _LOGGER.debug("ensuring connection") 330 | try: 331 | await self._ensure_connected() 332 | _LOGGER.debug("ensured connection - initialising") 333 | await self.initialise() 334 | except BleakNotFoundError: 335 | _LOGGER.debug("failed to ensure connection - backing off") 336 | await asyncio.sleep(BLEAK_BACKOFF_TIME) 337 | _LOGGER.debug("reconnecting again") 338 | asyncio.create_task(self._reconnect()) 339 | 340 | def intify(self, state: bytes) -> int: 341 | return int.from_bytes(state, byteorder="little") 342 | 343 | def _notification_handler(self, _sender: int, data: bytearray) -> None: 344 | """Handle notification responses.""" 345 | _LOGGER.debug("%s: Notification received: %s", self.name, data.hex()) 346 | 347 | self._buf += data 348 | msg = re.search(frame_regex, self._buf) 349 | if msg: 350 | self._buf = self._buf[msg.end() :] # noqa: E203 351 | target_state = msg.group("target_state") 352 | engineering_data = msg.group("engineering_data") 353 | 354 | target_state_int = self.intify(target_state) 355 | is_moving = bool(target_state_int & MOVING_TARGET) 356 | is_static = bool(target_state_int & STATIC_TARGET) 357 | moving_target_distance = self.intify(msg.group("moving_target_distance")) 358 | moving_target_energy = self.intify(msg.group("moving_target_energy")) 359 | static_target_distance = self.intify(msg.group("static_target_distance")) 360 | static_target_energy = self.intify(msg.group("static_target_energy")) 361 | detection_distance = self.intify(msg.group("detection_distance")) 362 | 363 | max_motion_gates = None 364 | max_static_gates = None 365 | motion_energy_gates = None 366 | static_energy_gates = None 367 | if engineering_data: 368 | em = re.match(engineering_frame_regex, engineering_data) 369 | if em: 370 | max_motion_gates = self.intify(em.group("maximum_motion_gates")) 371 | max_static_gates = self.intify(em.group("maximum_static_gates")) 372 | motion_energy_gates = [x for x in em.group("motion_energy_gates")] 373 | static_energy_gates = [x for x in em.group("static_energy_gates")] 374 | 375 | self._state = LD2410BLEState( 376 | is_moving=is_moving, 377 | is_static=is_static, 378 | moving_target_distance=moving_target_distance, 379 | moving_target_energy=moving_target_energy, 380 | static_target_distance=static_target_distance, 381 | static_target_energy=static_target_energy, 382 | detection_distance=detection_distance, 383 | max_motion_gates=max_motion_gates, 384 | max_static_gates=max_static_gates, 385 | motion_energy_gates=motion_energy_gates, 386 | static_energy_gates=static_energy_gates, 387 | ) 388 | 389 | self._fire_callbacks() 390 | 391 | _LOGGER.debug( 392 | "%s: Notification received; RSSI: %s: %s %s", 393 | self.name, 394 | self.rssi, 395 | data.hex(), 396 | self._state, 397 | ) 398 | 399 | def _disconnected(self, client: BleakClientWithServiceCache) -> None: 400 | """Disconnected callback.""" 401 | self._fire_disconnected_callbacks() 402 | if self._expected_disconnect: 403 | _LOGGER.debug( 404 | "%s: Disconnected from device; RSSI: %s", self.name, self.rssi 405 | ) 406 | return 407 | _LOGGER.warning( 408 | "%s: Device unexpectedly disconnected; RSSI: %s", 409 | self.name, 410 | self.rssi, 411 | ) 412 | asyncio.create_task(self._reconnect()) 413 | 414 | def _disconnect(self) -> None: 415 | """Disconnect from device.""" 416 | asyncio.create_task(self._execute_timed_disconnect()) 417 | 418 | async def _execute_timed_disconnect(self) -> None: 419 | """Execute timed disconnection.""" 420 | _LOGGER.debug( 421 | "%s: Disconnecting", 422 | self.name, 423 | ) 424 | await self._execute_disconnect() 425 | 426 | async def _execute_disconnect(self) -> None: 427 | """Execute disconnection.""" 428 | async with self._connect_lock: 429 | client = self._client 430 | self._expected_disconnect = True 431 | self._client = None 432 | if client and client.is_connected: 433 | await client.stop_notify(CHARACTERISTIC_NOTIFY) 434 | await client.disconnect() 435 | 436 | @retry_bluetooth_connection_error(DEFAULT_ATTEMPTS) 437 | async def _send_command_locked(self, commands: list[bytes]) -> None: 438 | """Send command to device and read response.""" 439 | try: 440 | await self._execute_command_locked(commands) 441 | except BleakDBusError as ex: 442 | # Disconnect so we can reset state and try again 443 | await asyncio.sleep(BLEAK_BACKOFF_TIME) 444 | _LOGGER.debug( 445 | "%s: RSSI: %s; Backing off %ss; Disconnecting due to error: %s", 446 | self.name, 447 | self.rssi, 448 | BLEAK_BACKOFF_TIME, 449 | ex, 450 | ) 451 | await self._execute_disconnect() 452 | raise 453 | except BleakError as ex: 454 | # Disconnect so we can reset state and try again 455 | _LOGGER.debug( 456 | "%s: RSSI: %s; Disconnecting due to error: %s", self.name, self.rssi, ex 457 | ) 458 | await self._execute_disconnect() 459 | raise 460 | 461 | async def _send_command( 462 | self, commands: list[bytes] | bytes, retry: int | None = None 463 | ) -> None: 464 | """Send command to device and read response.""" 465 | await self._ensure_connected() 466 | if not isinstance(commands, list): 467 | commands = [commands] 468 | await self._send_command_while_connected(commands, retry) 469 | 470 | async def _send_command_while_connected( 471 | self, commands: list[bytes], retry: int | None = None 472 | ) -> None: 473 | """Send command to device and read response.""" 474 | _LOGGER.debug( 475 | "%s: Sending commands %s", 476 | self.name, 477 | [command.hex() for command in commands], 478 | ) 479 | if self._operation_lock.locked(): 480 | _LOGGER.debug( 481 | "%s: Operation already in progress, waiting for it to complete; RSSI: %s", 482 | self.name, 483 | self.rssi, 484 | ) 485 | async with self._operation_lock: 486 | try: 487 | await self._send_command_locked(commands) 488 | return 489 | except BleakNotFoundError: 490 | _LOGGER.error( 491 | "%s: device not found, no longer in range, or poor RSSI: %s", 492 | self.name, 493 | self.rssi, 494 | exc_info=True, 495 | ) 496 | raise 497 | except CharacteristicMissingError as ex: 498 | _LOGGER.debug( 499 | "%s: characteristic missing: %s; RSSI: %s", 500 | self.name, 501 | ex, 502 | self.rssi, 503 | exc_info=True, 504 | ) 505 | raise 506 | except BLEAK_EXCEPTIONS: 507 | _LOGGER.debug("%s: communication failed", self.name, exc_info=True) 508 | raise 509 | 510 | raise RuntimeError("Unreachable") 511 | 512 | async def _execute_command_locked(self, commands: list[bytes]) -> None: 513 | """Execute command and read response.""" 514 | assert self._client is not None # nosec 515 | for command in commands: 516 | await self._client.write_gatt_char(CHARACTERISTIC_WRITE, command, False) 517 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "alabaster" 3 | version = "0.7.12" 4 | description = "A configurable sidebar-enabled Sphinx theme" 5 | category = "main" 6 | optional = true 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "async-timeout" 11 | version = "4.0.2" 12 | description = "Timeout context manager for asyncio programs" 13 | category = "main" 14 | optional = false 15 | python-versions = ">=3.6" 16 | 17 | [[package]] 18 | name = "atomicwrites" 19 | version = "1.4.1" 20 | description = "Atomic file writes." 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | 25 | [[package]] 26 | name = "attrs" 27 | version = "21.4.0" 28 | description = "Classes Without Boilerplate" 29 | category = "dev" 30 | optional = false 31 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 32 | 33 | [package.extras] 34 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] 35 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 36 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] 37 | tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] 38 | 39 | [[package]] 40 | name = "babel" 41 | version = "2.10.3" 42 | description = "Internationalization utilities" 43 | category = "main" 44 | optional = true 45 | python-versions = ">=3.6" 46 | 47 | [package.dependencies] 48 | pytz = ">=2015.7" 49 | 50 | [[package]] 51 | name = "bleak" 52 | version = "0.19.0" 53 | description = "Bluetooth Low Energy platform Agnostic Klient" 54 | category = "main" 55 | optional = false 56 | python-versions = ">=3.7,<4.0" 57 | 58 | [package.dependencies] 59 | async-timeout = ">=3.0.0,<5" 60 | bleak-winrt = {version = ">=1.2.0,<2.0.0", markers = "platform_system == \"Windows\""} 61 | dbus-fast = {version = ">=1.22.0,<2.0.0", markers = "platform_system == \"Linux\""} 62 | pyobjc-core = {version = ">=8.5.1,<9.0.0", markers = "platform_system == \"Darwin\""} 63 | pyobjc-framework-CoreBluetooth = {version = ">=8.5.1,<9.0.0", markers = "platform_system == \"Darwin\""} 64 | pyobjc-framework-libdispatch = {version = ">=8.5.1,<9.0.0", markers = "platform_system == \"Darwin\""} 65 | 66 | [[package]] 67 | name = "bleak-retry-connector" 68 | version = "2.3.0" 69 | description = "A connector for Bleak Clients that handles transient connection failures" 70 | category = "main" 71 | optional = false 72 | python-versions = ">=3.9,<4.0" 73 | 74 | [package.dependencies] 75 | async-timeout = ">=4.0.1" 76 | bleak = ">=0.19.0" 77 | dbus-fast = {version = ">=1.14.0", markers = "platform_system == \"Linux\""} 78 | 79 | [package.extras] 80 | docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] 81 | 82 | [[package]] 83 | name = "bleak-winrt" 84 | version = "1.2.0" 85 | description = "Python WinRT bindings for Bleak" 86 | category = "main" 87 | optional = false 88 | python-versions = "*" 89 | 90 | [[package]] 91 | name = "certifi" 92 | version = "2022.6.15" 93 | description = "Python package for providing Mozilla's CA Bundle." 94 | category = "main" 95 | optional = true 96 | python-versions = ">=3.6" 97 | 98 | [[package]] 99 | name = "charset-normalizer" 100 | version = "2.1.0" 101 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 102 | category = "main" 103 | optional = true 104 | python-versions = ">=3.6.0" 105 | 106 | [package.extras] 107 | unicode_backport = ["unicodedata2"] 108 | 109 | [[package]] 110 | name = "colorama" 111 | version = "0.4.5" 112 | description = "Cross-platform colored terminal text." 113 | category = "main" 114 | optional = false 115 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 116 | 117 | [[package]] 118 | name = "coverage" 119 | version = "6.4.4" 120 | description = "Code coverage measurement for Python" 121 | category = "dev" 122 | optional = false 123 | python-versions = ">=3.7" 124 | 125 | [package.dependencies] 126 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 127 | 128 | [package.extras] 129 | toml = ["tomli"] 130 | 131 | [[package]] 132 | name = "dbus-fast" 133 | version = "1.45.0" 134 | description = "A faster version of dbus-next" 135 | category = "main" 136 | optional = false 137 | python-versions = ">=3.7,<4.0" 138 | 139 | [package.dependencies] 140 | async-timeout = ">=3.0.0" 141 | 142 | [package.extras] 143 | docs = ["Sphinx (>=5.1.1,<6.0.0)", "myst-parser (>=0.18.0,<0.19.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinxcontrib-fulltoc (>=1.2.0,<2.0.0)"] 144 | 145 | [[package]] 146 | name = "docutils" 147 | version = "0.17.1" 148 | description = "Docutils -- Python Documentation Utilities" 149 | category = "main" 150 | optional = true 151 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 152 | 153 | [[package]] 154 | name = "flux-led" 155 | version = "0.28.32" 156 | description = "A Python library to communicate with the flux_led smart bulbs" 157 | category = "main" 158 | optional = false 159 | python-versions = ">=3.7" 160 | 161 | [package.dependencies] 162 | webcolors = "*" 163 | 164 | [package.extras] 165 | all = ["Sphinx (>=3.4.3)", "black (>=19.10b0)", "bump2version (>=1.0.1)", "codecov (>=2.1.4)", "coverage (>=5.1)", "flake8 (>=3.8.3)", "flake8-debugger (>=3.2.1)", "ipython (>=7.15.0)", "m2r2 (>=0.2.7)", "pytest (>=5.4.3)", "pytest-asyncio", "pytest-cov (>=2.9.0)", "pytest-raises (>=0.11)", "pytest-runner (>=5.2)", "sphinx-rtd-theme (>=0.5.1)", "tox (>=3.15.2)", "twine (>=3.1.1)", "typing-extensions", "webcolors", "wheel (>=0.34.2)"] 166 | dev = ["Sphinx (>=3.4.3)", "black (>=19.10b0)", "bump2version (>=1.0.1)", "codecov (>=2.1.4)", "coverage (>=5.1)", "flake8 (>=3.8.3)", "flake8-debugger (>=3.2.1)", "ipython (>=7.15.0)", "m2r2 (>=0.2.7)", "pytest (>=5.4.3)", "pytest-asyncio", "pytest-cov (>=2.9.0)", "pytest-raises (>=0.11)", "pytest-runner (>=5.2)", "sphinx-rtd-theme (>=0.5.1)", "tox (>=3.15.2)", "twine (>=3.1.1)", "wheel (>=0.34.2)"] 167 | setup = ["pytest-runner (>=5.2)"] 168 | test = ["black (>=19.10b0)", "codecov (>=2.1.4)", "flake8 (>=3.8.3)", "flake8-debugger (>=3.2.1)", "pytest (>=5.4.3)", "pytest-asyncio", "pytest-cov (>=2.9.0)", "pytest-raises (>=0.11)"] 169 | 170 | [[package]] 171 | name = "idna" 172 | version = "3.3" 173 | description = "Internationalized Domain Names in Applications (IDNA)" 174 | category = "main" 175 | optional = true 176 | python-versions = ">=3.5" 177 | 178 | [[package]] 179 | name = "imagesize" 180 | version = "1.4.1" 181 | description = "Getting image size from png/jpeg/jpeg2000/gif file" 182 | category = "main" 183 | optional = true 184 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 185 | 186 | [[package]] 187 | name = "importlib-metadata" 188 | version = "4.12.0" 189 | description = "Read metadata from Python packages" 190 | category = "main" 191 | optional = true 192 | python-versions = ">=3.7" 193 | 194 | [package.dependencies] 195 | zipp = ">=0.5" 196 | 197 | [package.extras] 198 | docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] 199 | perf = ["ipython"] 200 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] 201 | 202 | [[package]] 203 | name = "iniconfig" 204 | version = "1.1.1" 205 | description = "iniconfig: brain-dead simple config-ini parsing" 206 | category = "dev" 207 | optional = false 208 | python-versions = "*" 209 | 210 | [[package]] 211 | name = "jinja2" 212 | version = "3.1.2" 213 | description = "A very fast and expressive template engine." 214 | category = "main" 215 | optional = true 216 | python-versions = ">=3.7" 217 | 218 | [package.dependencies] 219 | MarkupSafe = ">=2.0" 220 | 221 | [package.extras] 222 | i18n = ["Babel (>=2.7)"] 223 | 224 | [[package]] 225 | name = "markdown-it-py" 226 | version = "2.1.0" 227 | description = "Python port of markdown-it. Markdown parsing, done right!" 228 | category = "main" 229 | optional = true 230 | python-versions = ">=3.7" 231 | 232 | [package.dependencies] 233 | mdurl = ">=0.1,<1.0" 234 | 235 | [package.extras] 236 | benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] 237 | code_style = ["pre-commit (==2.6)"] 238 | compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] 239 | linkify = ["linkify-it-py (>=1.0,<2.0)"] 240 | plugins = ["mdit-py-plugins"] 241 | profiling = ["gprof2dot"] 242 | rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 243 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 244 | 245 | [[package]] 246 | name = "markupsafe" 247 | version = "2.1.1" 248 | description = "Safely add untrusted strings to HTML/XML markup." 249 | category = "main" 250 | optional = true 251 | python-versions = ">=3.7" 252 | 253 | [[package]] 254 | name = "mdit-py-plugins" 255 | version = "0.3.0" 256 | description = "Collection of plugins for markdown-it-py" 257 | category = "main" 258 | optional = true 259 | python-versions = "~=3.6" 260 | 261 | [package.dependencies] 262 | markdown-it-py = ">=1.0.0,<3.0.0" 263 | 264 | [package.extras] 265 | code_style = ["pre-commit (==2.6)"] 266 | rtd = ["myst-parser (>=0.14.0,<0.15.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] 267 | testing = ["coverage", "pytest (>=3.6,<4)", "pytest-cov", "pytest-regressions"] 268 | 269 | [[package]] 270 | name = "mdurl" 271 | version = "0.1.1" 272 | description = "Markdown URL utilities" 273 | category = "main" 274 | optional = true 275 | python-versions = ">=3.7" 276 | 277 | [[package]] 278 | name = "myst-parser" 279 | version = "0.18.0" 280 | description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." 281 | category = "main" 282 | optional = true 283 | python-versions = ">=3.7" 284 | 285 | [package.dependencies] 286 | docutils = ">=0.15,<0.19" 287 | jinja2 = "*" 288 | markdown-it-py = ">=1.0.0,<3.0.0" 289 | mdit-py-plugins = ">=0.3.0,<0.4.0" 290 | pyyaml = "*" 291 | sphinx = ">=4,<6" 292 | typing-extensions = "*" 293 | 294 | [package.extras] 295 | code_style = ["pre-commit (>=2.12,<3.0)"] 296 | linkify = ["linkify-it-py (>=1.0,<2.0)"] 297 | rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] 298 | testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] 299 | 300 | [[package]] 301 | name = "packaging" 302 | version = "21.3" 303 | description = "Core utilities for Python packages" 304 | category = "main" 305 | optional = false 306 | python-versions = ">=3.6" 307 | 308 | [package.dependencies] 309 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 310 | 311 | [[package]] 312 | name = "pluggy" 313 | version = "1.0.0" 314 | description = "plugin and hook calling mechanisms for python" 315 | category = "dev" 316 | optional = false 317 | python-versions = ">=3.6" 318 | 319 | [package.extras] 320 | dev = ["pre-commit", "tox"] 321 | testing = ["pytest", "pytest-benchmark"] 322 | 323 | [[package]] 324 | name = "py" 325 | version = "1.11.0" 326 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 327 | category = "dev" 328 | optional = false 329 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 330 | 331 | [[package]] 332 | name = "pygments" 333 | version = "2.12.0" 334 | description = "Pygments is a syntax highlighting package written in Python." 335 | category = "main" 336 | optional = true 337 | python-versions = ">=3.6" 338 | 339 | [[package]] 340 | name = "pyobjc-core" 341 | version = "8.5.1" 342 | description = "Python<->ObjC Interoperability Module" 343 | category = "main" 344 | optional = false 345 | python-versions = ">=3.6" 346 | 347 | [[package]] 348 | name = "pyobjc-framework-Cocoa" 349 | version = "8.5.1" 350 | description = "Wrappers for the Cocoa frameworks on macOS" 351 | category = "main" 352 | optional = false 353 | python-versions = ">=3.6" 354 | 355 | [package.dependencies] 356 | pyobjc-core = ">=8.5.1" 357 | 358 | [[package]] 359 | name = "pyobjc-framework-CoreBluetooth" 360 | version = "8.5.1" 361 | description = "Wrappers for the framework CoreBluetooth on macOS" 362 | category = "main" 363 | optional = false 364 | python-versions = ">=3.6" 365 | 366 | [package.dependencies] 367 | pyobjc-core = ">=8.5.1" 368 | pyobjc-framework-Cocoa = ">=8.5.1" 369 | 370 | [[package]] 371 | name = "pyobjc-framework-libdispatch" 372 | version = "8.5.1" 373 | description = "Wrappers for libdispatch on macOS" 374 | category = "main" 375 | optional = false 376 | python-versions = ">=3.6" 377 | 378 | [package.dependencies] 379 | pyobjc-core = ">=8.5.1" 380 | 381 | [[package]] 382 | name = "pyparsing" 383 | version = "3.0.9" 384 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 385 | category = "main" 386 | optional = false 387 | python-versions = ">=3.6.8" 388 | 389 | [package.extras] 390 | diagrams = ["jinja2", "railroad-diagrams"] 391 | 392 | [[package]] 393 | name = "pytest" 394 | version = "7.1.2" 395 | description = "pytest: simple powerful testing with Python" 396 | category = "dev" 397 | optional = false 398 | python-versions = ">=3.7" 399 | 400 | [package.dependencies] 401 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 402 | attrs = ">=19.2.0" 403 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 404 | iniconfig = "*" 405 | packaging = "*" 406 | pluggy = ">=0.12,<2.0" 407 | py = ">=1.8.2" 408 | tomli = ">=1.0.0" 409 | 410 | [package.extras] 411 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 412 | 413 | [[package]] 414 | name = "pytest-cov" 415 | version = "3.0.0" 416 | description = "Pytest plugin for measuring coverage." 417 | category = "dev" 418 | optional = false 419 | python-versions = ">=3.6" 420 | 421 | [package.dependencies] 422 | coverage = {version = ">=5.2.1", extras = ["toml"]} 423 | pytest = ">=4.6" 424 | 425 | [package.extras] 426 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 427 | 428 | [[package]] 429 | name = "pytz" 430 | version = "2022.1" 431 | description = "World timezone definitions, modern and historical" 432 | category = "main" 433 | optional = true 434 | python-versions = "*" 435 | 436 | [[package]] 437 | name = "pyyaml" 438 | version = "6.0" 439 | description = "YAML parser and emitter for Python" 440 | category = "main" 441 | optional = true 442 | python-versions = ">=3.6" 443 | 444 | [[package]] 445 | name = "requests" 446 | version = "2.28.1" 447 | description = "Python HTTP for Humans." 448 | category = "main" 449 | optional = true 450 | python-versions = ">=3.7, <4" 451 | 452 | [package.dependencies] 453 | certifi = ">=2017.4.17" 454 | charset-normalizer = ">=2,<3" 455 | idna = ">=2.5,<4" 456 | urllib3 = ">=1.21.1,<1.27" 457 | 458 | [package.extras] 459 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 460 | use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] 461 | 462 | [[package]] 463 | name = "snowballstemmer" 464 | version = "2.2.0" 465 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 466 | category = "main" 467 | optional = true 468 | python-versions = "*" 469 | 470 | [[package]] 471 | name = "sphinx" 472 | version = "5.1.1" 473 | description = "Python documentation generator" 474 | category = "main" 475 | optional = true 476 | python-versions = ">=3.6" 477 | 478 | [package.dependencies] 479 | alabaster = ">=0.7,<0.8" 480 | babel = ">=1.3" 481 | colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} 482 | docutils = ">=0.14,<0.20" 483 | imagesize = "*" 484 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 485 | Jinja2 = ">=2.3" 486 | packaging = "*" 487 | Pygments = ">=2.0" 488 | requests = ">=2.5.0" 489 | snowballstemmer = ">=1.1" 490 | sphinxcontrib-applehelp = "*" 491 | sphinxcontrib-devhelp = "*" 492 | sphinxcontrib-htmlhelp = ">=2.0.0" 493 | sphinxcontrib-jsmath = "*" 494 | sphinxcontrib-qthelp = "*" 495 | sphinxcontrib-serializinghtml = ">=1.1.5" 496 | 497 | [package.extras] 498 | docs = ["sphinxcontrib-websupport"] 499 | lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "isort", "mypy (>=0.971)", "sphinx-lint", "types-requests", "types-typed-ast"] 500 | test = ["cython", "html5lib", "pytest (>=4.6)", "typed-ast"] 501 | 502 | [[package]] 503 | name = "sphinx-rtd-theme" 504 | version = "1.0.0" 505 | description = "Read the Docs theme for Sphinx" 506 | category = "main" 507 | optional = true 508 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 509 | 510 | [package.dependencies] 511 | docutils = "<0.18" 512 | sphinx = ">=1.6" 513 | 514 | [package.extras] 515 | dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] 516 | 517 | [[package]] 518 | name = "sphinxcontrib-applehelp" 519 | version = "1.0.2" 520 | description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" 521 | category = "main" 522 | optional = true 523 | python-versions = ">=3.5" 524 | 525 | [package.extras] 526 | lint = ["docutils-stubs", "flake8", "mypy"] 527 | test = ["pytest"] 528 | 529 | [[package]] 530 | name = "sphinxcontrib-devhelp" 531 | version = "1.0.2" 532 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." 533 | category = "main" 534 | optional = true 535 | python-versions = ">=3.5" 536 | 537 | [package.extras] 538 | lint = ["docutils-stubs", "flake8", "mypy"] 539 | test = ["pytest"] 540 | 541 | [[package]] 542 | name = "sphinxcontrib-htmlhelp" 543 | version = "2.0.0" 544 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" 545 | category = "main" 546 | optional = true 547 | python-versions = ">=3.6" 548 | 549 | [package.extras] 550 | lint = ["docutils-stubs", "flake8", "mypy"] 551 | test = ["html5lib", "pytest"] 552 | 553 | [[package]] 554 | name = "sphinxcontrib-jsmath" 555 | version = "1.0.1" 556 | description = "A sphinx extension which renders display math in HTML via JavaScript" 557 | category = "main" 558 | optional = true 559 | python-versions = ">=3.5" 560 | 561 | [package.extras] 562 | test = ["flake8", "mypy", "pytest"] 563 | 564 | [[package]] 565 | name = "sphinxcontrib-qthelp" 566 | version = "1.0.3" 567 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." 568 | category = "main" 569 | optional = true 570 | python-versions = ">=3.5" 571 | 572 | [package.extras] 573 | lint = ["docutils-stubs", "flake8", "mypy"] 574 | test = ["pytest"] 575 | 576 | [[package]] 577 | name = "sphinxcontrib-serializinghtml" 578 | version = "1.1.5" 579 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." 580 | category = "main" 581 | optional = true 582 | python-versions = ">=3.5" 583 | 584 | [package.extras] 585 | lint = ["docutils-stubs", "flake8", "mypy"] 586 | test = ["pytest"] 587 | 588 | [[package]] 589 | name = "tomli" 590 | version = "2.0.1" 591 | description = "A lil' TOML parser" 592 | category = "dev" 593 | optional = false 594 | python-versions = ">=3.7" 595 | 596 | [[package]] 597 | name = "typing-extensions" 598 | version = "4.3.0" 599 | description = "Backported and Experimental Type Hints for Python 3.7+" 600 | category = "main" 601 | optional = true 602 | python-versions = ">=3.7" 603 | 604 | [[package]] 605 | name = "urllib3" 606 | version = "1.26.11" 607 | description = "HTTP library with thread-safe connection pooling, file post, and more." 608 | category = "main" 609 | optional = true 610 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" 611 | 612 | [package.extras] 613 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 614 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] 615 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 616 | 617 | [[package]] 618 | name = "webcolors" 619 | version = "1.12" 620 | description = "A library for working with color names and color values formats defined by HTML and CSS." 621 | category = "main" 622 | optional = false 623 | python-versions = ">=3.7" 624 | 625 | [[package]] 626 | name = "zipp" 627 | version = "3.8.1" 628 | description = "Backport of pathlib-compatible object wrapper for zip files" 629 | category = "main" 630 | optional = true 631 | python-versions = ">=3.7" 632 | 633 | [package.extras] 634 | docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] 635 | testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 636 | 637 | [extras] 638 | docs = ["myst-parser", "Sphinx", "sphinx-rtd-theme"] 639 | 640 | [metadata] 641 | lock-version = "1.1" 642 | python-versions = "^3.9" 643 | content-hash = "cc1b77fa653e3c4a09c36133586dfc1bd6ec69284c6cbfa61235c85db1b3d901" 644 | 645 | [metadata.files] 646 | alabaster = [ 647 | {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, 648 | {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, 649 | ] 650 | async-timeout = [ 651 | {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, 652 | {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, 653 | ] 654 | atomicwrites = [ 655 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 656 | ] 657 | attrs = [ 658 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 659 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 660 | ] 661 | babel = [ 662 | {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, 663 | {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, 664 | ] 665 | bleak = [ 666 | {file = "bleak-0.19.0-py3-none-any.whl", hash = "sha256:ccdba0d17dcceb1326e4e46600b37e9019cd52ce01948e2a3dbd6c94d1e4de01"}, 667 | {file = "bleak-0.19.0.tar.gz", hash = "sha256:cce5200ca9bac7daaa74dd009c867c8c2b161a124e234c74307462e86caf50e6"}, 668 | ] 669 | bleak-retry-connector = [ 670 | {file = "bleak_retry_connector-2.3.0-py3-none-any.whl", hash = "sha256:251bd30720e908ec371c8ad5e4893a14d4e7700897d3a4a4e29f0722b2d2c499"}, 671 | {file = "bleak_retry_connector-2.3.0.tar.gz", hash = "sha256:58c159aa3b3ecef9f3d63beb19923279ab3c77c7cca3a0cc82cc6daae3c93637"}, 672 | ] 673 | bleak-winrt = [ 674 | {file = "bleak-winrt-1.2.0.tar.gz", hash = "sha256:0577d070251b9354fc6c45ffac57e39341ebb08ead014b1bdbd43e211d2ce1d6"}, 675 | {file = "bleak_winrt-1.2.0-cp310-cp310-win32.whl", hash = "sha256:a2ae3054d6843ae0cfd3b94c83293a1dfd5804393977dd69bde91cb5099fc47c"}, 676 | {file = "bleak_winrt-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:677df51dc825c6657b3ae94f00bd09b8ab88422b40d6a7bdbf7972a63bc44e9a"}, 677 | {file = "bleak_winrt-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9449cdb942f22c9892bc1ada99e2ccce9bea8a8af1493e81fefb6de2cb3a7b80"}, 678 | {file = "bleak_winrt-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:98c1b5a6a6c431ac7f76aa4285b752fe14a1c626bd8a1dfa56f66173ff120bee"}, 679 | {file = "bleak_winrt-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:623ac511696e1f58d83cb9c431e32f613395f2199b3db7f125a3d872cab968a4"}, 680 | {file = "bleak_winrt-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:13ab06dec55469cf51a2c187be7b630a7a2922e1ea9ac1998135974a7239b1e3"}, 681 | {file = "bleak_winrt-1.2.0-cp38-cp38-win32.whl", hash = "sha256:5a36ff8cd53068c01a795a75d2c13054ddc5f99ce6de62c1a97cd343fc4d0727"}, 682 | {file = "bleak_winrt-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:810c00726653a962256b7acd8edf81ab9e4a3c66e936a342ce4aec7dbd3a7263"}, 683 | {file = "bleak_winrt-1.2.0-cp39-cp39-win32.whl", hash = "sha256:dd740047a08925bde54bec357391fcee595d7b8ca0c74c87170a5cbc3f97aa0a"}, 684 | {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, 685 | ] 686 | certifi = [ 687 | {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, 688 | {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, 689 | ] 690 | charset-normalizer = [ 691 | {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, 692 | {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, 693 | ] 694 | colorama = [ 695 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 696 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 697 | ] 698 | coverage = [ 699 | {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, 700 | {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, 701 | {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, 702 | {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, 703 | {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, 704 | {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, 705 | {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, 706 | {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, 707 | {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, 708 | {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, 709 | {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, 710 | {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, 711 | {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, 712 | {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, 713 | {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, 714 | {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, 715 | {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, 716 | {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, 717 | {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, 718 | {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, 719 | {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, 720 | {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, 721 | {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, 722 | {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, 723 | {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, 724 | {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, 725 | {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, 726 | {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, 727 | {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, 728 | {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, 729 | {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, 730 | {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, 731 | {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, 732 | {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, 733 | {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, 734 | {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, 735 | {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, 736 | {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, 737 | {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, 738 | {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, 739 | {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, 740 | {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, 741 | {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, 742 | {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, 743 | {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, 744 | {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, 745 | {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, 746 | {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, 747 | {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, 748 | {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, 749 | ] 750 | dbus-fast = [ 751 | {file = "dbus_fast-1.45.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:a80d284d72ebc9893943e6be705866d72dfa37ce7534cd274e7ab1cd86b0a829"}, 752 | {file = "dbus_fast-1.45.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8250cfafb1f875fe0204bbea49419ee8c72ff834c40f22765ac39c54ba1b8fd3"}, 753 | {file = "dbus_fast-1.45.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5d092874c16d5f64dd5c164cbf84da151a6565fa21aa518da8cf4178e2b9df0c"}, 754 | {file = "dbus_fast-1.45.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552e145b1fdb98922cc83b24fb8677ee63029628f19bb7b2f1aca075c3208945"}, 755 | {file = "dbus_fast-1.45.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:eb30d3d70deff8145ceedad15b7030fcc0d9b5411b84f0b80ba1f6581f83fec0"}, 756 | {file = "dbus_fast-1.45.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40050d56f59be198486fc0ad1a7b4aba16928a31c89f84e98cd58ea332186a9f"}, 757 | {file = "dbus_fast-1.45.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10bc728544b95204abb9717941272146a9ba671ee7e6e93a697e4a17e93840c2"}, 758 | {file = "dbus_fast-1.45.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ca3b008892aff4e0fa5a8046a8492043f5108502f3105a09f676749855d130e"}, 759 | {file = "dbus_fast-1.45.0-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:24561a1a31de35fef7d59b285e5e5b5c1c76b7eb4e9047dcaf0a726627204b6c"}, 760 | {file = "dbus_fast-1.45.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c6286bd0a9a3ef0a65dfb666df07bd77efa0d6f1798afe5dabc14437cd5ed6a"}, 761 | {file = "dbus_fast-1.45.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:48e09ae676c87ca4fbc58881f0c0d6c41a67d87610878c9ed3a6692fc0368b29"}, 762 | {file = "dbus_fast-1.45.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a1d81da92f2438164b8ebea566972e0706313e46490f8cc2a2c5480adc50b238"}, 763 | {file = "dbus_fast-1.45.0-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:9a838a2bb353079ad4a040c697099be6481d908ae063810d28e086c259bd2786"}, 764 | {file = "dbus_fast-1.45.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69a6b90ba45ace831cb6e9b5ad0c0850fed8bec95591d35edafedbeac323135"}, 765 | {file = "dbus_fast-1.45.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ed0d2c86fe4c98c8f685adab6b4c18307855b7198fe8cd78971e8e1dac1e3c68"}, 766 | {file = "dbus_fast-1.45.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:77ca7d27648a09fc38e54aa6289a1eb32214ac72e64bfa46a12c6161ddd3c984"}, 767 | {file = "dbus_fast-1.45.0-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b673c1810010625ef8e9a0df314267b9e7b597828412caa337716efebbdfbf05"}, 768 | {file = "dbus_fast-1.45.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3292370b9f07da9bced35915b0e5b3930d24a642d18400462ab3be4af9ef552c"}, 769 | {file = "dbus_fast-1.45.0-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:9323a530b952aa0a6e5cd9cb00b150f74cc2ea2c519fe01e13d5d635f2607d9a"}, 770 | {file = "dbus_fast-1.45.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a77dcb8c11e1685ae1d3600b9e8260ca5c9daaa9526664112af2f1bdcbee280d"}, 771 | {file = "dbus_fast-1.45.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ff3c131469b6f90a52c953d853a0a97dced5dc6678496b12657aaa1e014ca78"}, 772 | {file = "dbus_fast-1.45.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:2685caab65ddb2e7b3aed6dc17a214bb1f7cfd6164f4895bee167ecd5463d71e"}, 773 | {file = "dbus_fast-1.45.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5bef4860104bc3580e49a70928d6f05085c9911f842efe12f099539092a4f94"}, 774 | {file = "dbus_fast-1.45.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:2cfdabca35a48172d19c56bc149c9ce76f94a747e243a3cfd7c115751988800d"}, 775 | {file = "dbus_fast-1.45.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27eff54c50e752e213d98dbf9dfc2d98a8109dfa9c1314eab5074aef3ea45f51"}, 776 | {file = "dbus_fast-1.45.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:2f7ede9ad484d38d63d33b5b54757a885d6a36569d5cfed6d1a7969576396d21"}, 777 | {file = "dbus_fast-1.45.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed5e612d0c66fe204030b5c6513cd132203d0a32030e2b6d9ed84b26409959aa"}, 778 | {file = "dbus_fast-1.45.0.tar.gz", hash = "sha256:ed5204b265f316f1a5ab52de3d857038532945b4645a0ff73e6dbb9622747117"}, 779 | ] 780 | docutils = [ 781 | {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, 782 | {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, 783 | ] 784 | flux-led = [ 785 | {file = "flux_led-0.28.32-py3-none-any.whl", hash = "sha256:0a8a2f52a1e494f5273ba2d5cc5955f39cc948ffe1e81152b1115bcad67c94e0"}, 786 | {file = "flux_led-0.28.32.tar.gz", hash = "sha256:931a690b570a93ccb1c71d62786dafd6f5e25358edcabaea41ed5a17a0b2a2ba"}, 787 | ] 788 | idna = [ 789 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 790 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 791 | ] 792 | imagesize = [ 793 | {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, 794 | {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, 795 | ] 796 | importlib-metadata = [ 797 | {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, 798 | {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, 799 | ] 800 | iniconfig = [ 801 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 802 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 803 | ] 804 | jinja2 = [ 805 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 806 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 807 | ] 808 | markdown-it-py = [ 809 | {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, 810 | {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, 811 | ] 812 | markupsafe = [ 813 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, 814 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, 815 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, 816 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, 817 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, 818 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, 819 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, 820 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, 821 | {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, 822 | {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, 823 | {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, 824 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, 825 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, 826 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, 827 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, 828 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, 829 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, 830 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, 831 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, 832 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, 833 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, 834 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, 835 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, 836 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, 837 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, 838 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, 839 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, 840 | {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, 841 | {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, 842 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, 843 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, 844 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, 845 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, 846 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, 847 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, 848 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, 849 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, 850 | {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, 851 | {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, 852 | {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, 853 | ] 854 | mdit-py-plugins = [ 855 | {file = "mdit-py-plugins-0.3.0.tar.gz", hash = "sha256:ecc24f51eeec6ab7eecc2f9724e8272c2fb191c2e93cf98109120c2cace69750"}, 856 | {file = "mdit_py_plugins-0.3.0-py3-none-any.whl", hash = "sha256:b1279701cee2dbf50e188d3da5f51fee8d78d038cdf99be57c6b9d1aa93b4073"}, 857 | ] 858 | mdurl = [ 859 | {file = "mdurl-0.1.1-py3-none-any.whl", hash = "sha256:6a8f6804087b7128040b2fb2ebe242bdc2affaeaa034d5fc9feeed30b443651b"}, 860 | {file = "mdurl-0.1.1.tar.gz", hash = "sha256:f79c9709944df218a4cdb0fcc0b0c7ead2f44594e3e84dc566606f04ad749c20"}, 861 | ] 862 | myst-parser = [ 863 | {file = "myst-parser-0.18.0.tar.gz", hash = "sha256:739a4d96773a8e55a2cacd3941ce46a446ee23dcd6b37e06f73f551ad7821d86"}, 864 | {file = "myst_parser-0.18.0-py3-none-any.whl", hash = "sha256:4965e51918837c13bf1c6f6fe2c6bddddf193148360fbdaefe743a4981358f6a"}, 865 | ] 866 | packaging = [ 867 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 868 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 869 | ] 870 | pluggy = [ 871 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 872 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 873 | ] 874 | py = [ 875 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 876 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 877 | ] 878 | pygments = [ 879 | {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, 880 | {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, 881 | ] 882 | pyobjc-core = [ 883 | {file = "pyobjc-core-8.5.1.tar.gz", hash = "sha256:f8592a12de076c27006700c4a46164478564fa33d7da41e7cbdd0a3bf9ddbccf"}, 884 | {file = "pyobjc_core-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b62dcf987cc511188fc2aa5b4d3b9fd895361ea4984380463497ce4b0752ddf4"}, 885 | {file = "pyobjc_core-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0accc653501a655f66c13f149a1d3d30e6cb65824edf852f7960a00c4f930d5b"}, 886 | {file = "pyobjc_core-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f82b32affc898e9e5af041c1cecde2c99f2ce160b87df77f678c99f1550a4655"}, 887 | {file = "pyobjc_core-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f7b2f6b6f3caeb882c658fe0c7098be2e8b79893d84daa8e636cb3e58a07df00"}, 888 | {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:872c0202c911a5a2f1269261c168e36569f6ddac17e5d854ac19e581726570cc"}, 889 | {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:21f92e231a4bae7f2d160d065f5afbf5e859a1e37f29d34ac12592205fc8c108"}, 890 | {file = "pyobjc_core-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:315334dd09781129af6a39641248891c4caa57043901750b0139c6614ce84ec0"}, 891 | ] 892 | pyobjc-framework-Cocoa = [ 893 | {file = "pyobjc-framework-Cocoa-8.5.1.tar.gz", hash = "sha256:9a3de5cdb4644e85daf53f2ed912ef6c16ea5804a9e65552eafe62c2e139eb8c"}, 894 | {file = "pyobjc_framework_Cocoa-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa572acc2628488a47be8d19f4701fc96fce7377cc4da18316e1e08c3918521a"}, 895 | {file = "pyobjc_framework_Cocoa-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb3ae21c8d81b7f02a891088c623cef61bca89bd671eff58c632d2f926b649f3"}, 896 | {file = "pyobjc_framework_Cocoa-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:88f08f5bd94c66d373d8413c1d08218aff4cff0b586e0cc4249b2284023e7577"}, 897 | {file = "pyobjc_framework_Cocoa-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:063683b57e4bd88cb0f9631ae65d25ec4eecf427d2fe8d0c578f88da9c896f3f"}, 898 | {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f8806ddfac40620fb27f185d0f8937e69e330617319ecc2eccf6b9c8451bdd1"}, 899 | {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7733a9a201df9e0cc2a0cf7bf54d76bd7981cba9b599353b243e3e0c9eefec10"}, 900 | {file = "pyobjc_framework_Cocoa-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f0ab227f99d3e25dd3db73f8cde0999914a5f0dd6a08600349d25f95eaa0da63"}, 901 | ] 902 | pyobjc-framework-CoreBluetooth = [ 903 | {file = "pyobjc-framework-CoreBluetooth-8.5.1.tar.gz", hash = "sha256:b4f621fc3b5bf289db58e64fd746773b18297f87a0ffc5502de74f69133301c1"}, 904 | {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bc720f2987a4d28dc73b13146e7c104d717100deb75c244da68f1d0849096661"}, 905 | {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2167f22886beb5b3ae69e475e055403f28eab065c49a25e2b98b050b483be799"}, 906 | {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aa9587a36eca143701731e8bb6c369148f8cc48c28168d41e7323828e5117f2d"}, 907 | ] 908 | pyobjc-framework-libdispatch = [ 909 | {file = "pyobjc-framework-libdispatch-8.5.1.tar.gz", hash = "sha256:066fb34fceb326307559104d45532ec2c7b55426f9910b70dbefd5d1b8fd530f"}, 910 | {file = "pyobjc_framework_libdispatch-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a316646ab30ba2a97bc828f8e27e7bb79efdf993d218a9c5118396b4f81dc762"}, 911 | {file = "pyobjc_framework_libdispatch-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7730a29e4d9c7d8c2e8d9ffb60af0ab6699b2186296d2bff0a2dd54527578bc3"}, 912 | {file = "pyobjc_framework_libdispatch-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:76208d9d2b0071df2950800495ac0300360bb5f25cbe9ab880b65cb809764979"}, 913 | {file = "pyobjc_framework_libdispatch-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1ad9aa4773ff1d89bf4385c081824c4f8708b50e3ac2fe0a9d590153242c0f67"}, 914 | {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81e1833bd26f15930faba678f9efdffafc79ec04e2ea8b6d1b88cafc0883af97"}, 915 | {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:73226e224436eb6383e7a8a811c90ed597995adb155b4f46d727881a383ac550"}, 916 | {file = "pyobjc_framework_libdispatch-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d115355ce446fc073c75cedfd7ab0a13958adda8e3a3b1e421e1f1e5f65640da"}, 917 | ] 918 | pyparsing = [ 919 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 920 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 921 | ] 922 | pytest = [ 923 | {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, 924 | {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, 925 | ] 926 | pytest-cov = [ 927 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 928 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 929 | ] 930 | pytz = [ 931 | {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, 932 | {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, 933 | ] 934 | pyyaml = [ 935 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 936 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 937 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 938 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 939 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 940 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 941 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 942 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 943 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 944 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 945 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 946 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 947 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 948 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 949 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 950 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 951 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 952 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 953 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 954 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 955 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 956 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 957 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 958 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 959 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 960 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 961 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 962 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 963 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 964 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 965 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 966 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 967 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 968 | ] 969 | requests = [ 970 | {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, 971 | {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, 972 | ] 973 | snowballstemmer = [ 974 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, 975 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, 976 | ] 977 | sphinx = [ 978 | {file = "Sphinx-5.1.1-py3-none-any.whl", hash = "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693"}, 979 | {file = "Sphinx-5.1.1.tar.gz", hash = "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89"}, 980 | ] 981 | sphinx-rtd-theme = [ 982 | {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, 983 | {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, 984 | ] 985 | sphinxcontrib-applehelp = [ 986 | {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, 987 | {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, 988 | ] 989 | sphinxcontrib-devhelp = [ 990 | {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, 991 | {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, 992 | ] 993 | sphinxcontrib-htmlhelp = [ 994 | {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, 995 | {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, 996 | ] 997 | sphinxcontrib-jsmath = [ 998 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, 999 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, 1000 | ] 1001 | sphinxcontrib-qthelp = [ 1002 | {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, 1003 | {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, 1004 | ] 1005 | sphinxcontrib-serializinghtml = [ 1006 | {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, 1007 | {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, 1008 | ] 1009 | tomli = [ 1010 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1011 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1012 | ] 1013 | typing-extensions = [ 1014 | {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, 1015 | {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, 1016 | ] 1017 | urllib3 = [ 1018 | {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, 1019 | {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, 1020 | ] 1021 | webcolors = [ 1022 | {file = "webcolors-1.12-py3-none-any.whl", hash = "sha256:d98743d81d498a2d3eaf165196e65481f0d2ea85281463d856b1e51b09f62dce"}, 1023 | {file = "webcolors-1.12.tar.gz", hash = "sha256:16d043d3a08fd6a1b1b7e3e9e62640d09790dce80d2bdd4792a175b35fe794a9"}, 1024 | ] 1025 | zipp = [ 1026 | {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, 1027 | {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, 1028 | ] 1029 | --------------------------------------------------------------------------------