pax_global_header 0000666 0000000 0000000 00000000064 15015405444 0014514 g ustar 00root root 0000000 0000000 52 comment=04d11e7aa56b167f300385199b8d4c0686256883
bthome-ble-3.13.0/ 0000775 0000000 0000000 00000000000 15015405444 0013616 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/.all-contributorsrc 0000664 0000000 0000000 00000000461 15015405444 0017450 0 ustar 00root root 0000000 0000000 {
"projectName": "bthome-ble",
"projectOwner": "bluetooth-devices",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 80,
"commit": true,
"commitConvention": "angular",
"contributors": [],
"contributorsPerLine": 7,
"skipCi": true
}
bthome-ble-3.13.0/.editorconfig 0000664 0000000 0000000 00000000444 15015405444 0016275 0 ustar 00root root 0000000 0000000 # http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
end_of_line = lf
[*.bat]
indent_style = tab
end_of_line = crlf
[LICENSE]
insert_final_newline = false
[Makefile]
indent_style = tab
bthome-ble-3.13.0/.flake8 0000664 0000000 0000000 00000000056 15015405444 0014772 0 ustar 00root root 0000000 0000000 [flake8]
exclude = docs
max-line-length = 100
bthome-ble-3.13.0/.github/ 0000775 0000000 0000000 00000000000 15015405444 0015156 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15015405444 0017341 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/.github/ISSUE_TEMPLATE/1-bug_report.md 0000664 0000000 0000000 00000000422 15015405444 0022167 0 ustar 00root root 0000000 0000000 ---
name: Bug report
about: Create a report to help us improve
labels: bug
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Additional context**
Add any other context about the problem here.
bthome-ble-3.13.0/.github/ISSUE_TEMPLATE/2-feature-request.md 0000664 0000000 0000000 00000000672 15015405444 0023150 0 ustar 00root root 0000000 0000000 ---
name: Feature request
about: Suggest an idea for this project
labels: enhancement
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.
bthome-ble-3.13.0/.github/dependabot.yml 0000664 0000000 0000000 00000001344 15015405444 0020010 0 ustar 00root root 0000000 0000000 # To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
commit-message:
prefix: "chore(ci): "
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
bthome-ble-3.13.0/.github/labels.toml 0000664 0000000 0000000 00000003515 15015405444 0017321 0 ustar 00root root 0000000 0000000 [breaking]
color = "ffcc00"
name = "breaking"
description = "Breaking change."
[bug]
color = "d73a4a"
name = "bug"
description = "Something isn't working"
[dependencies]
color = "0366d6"
name = "dependencies"
description = "Pull requests that update a dependency file"
[github_actions]
color = "000000"
name = "github_actions"
description = "Update of github actions"
[documentation]
color = "1bc4a5"
name = "documentation"
description = "Improvements or additions to documentation"
[duplicate]
color = "cfd3d7"
name = "duplicate"
description = "This issue or pull request already exists"
[enhancement]
color = "a2eeef"
name = "enhancement"
description = "New feature or request"
["good first issue"]
color = "7057ff"
name = "good first issue"
description = "Good for newcomers"
["help wanted"]
color = "008672"
name = "help wanted"
description = "Extra attention is needed"
[invalid]
color = "e4e669"
name = "invalid"
description = "This doesn't seem right"
[nochangelog]
color = "555555"
name = "nochangelog"
description = "Exclude pull requests from changelog"
[question]
color = "d876e3"
name = "question"
description = "Further information is requested"
[removed]
color = "e99695"
name = "removed"
description = "Removed piece of functionalities."
[tests]
color = "bfd4f2"
name = "tests"
description = "CI, CD and testing related changes"
[wontfix]
color = "ffffff"
name = "wontfix"
description = "This will not be worked on"
[discussion]
color = "c2e0c6"
name = "discussion"
description = "Some discussion around the project"
[hacktoberfest]
color = "ffa663"
name = "hacktoberfest"
description = "Good issues for Hacktoberfest"
[answered]
color = "0ee2b6"
name = "answered"
description = "Automatically closes as answered after a delay"
[waiting]
color = "5f7972"
name = "waiting"
description = "Automatically closes if no answer after a delay"
bthome-ble-3.13.0/.github/workflows/ 0000775 0000000 0000000 00000000000 15015405444 0017213 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000004340 15015405444 0020332 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches:
- main
pull_request:
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: pre-commit/action@v3.0.1
# Make sure commit messages follow the conventional commits convention:
# https://www.conventionalcommits.org
commitlint:
name: Lint Commit Messages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v6
test:
strategy:
fail-fast: false
matrix:
python-version:
- "3.11"
- "3.12"
- "3.13"
os:
- ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: snok/install-poetry@v1
- name: Install Dependencies
run: poetry install
- name: Test with Pytest
run: poetry run pytest --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
release:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: write
if: github.ref == 'refs/heads/main'
needs:
- test
- lint
- commitlint
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Python Semantic Release
id: release
uses: python-semantic-release/python-semantic-release@v9.21.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: steps.release.outputs.released == 'true'
- name: Publish package to GitHub Release
uses: python-semantic-release/publish-action@v9.21.0
if: steps.release.outputs.released == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
bthome-ble-3.13.0/.github/workflows/hacktoberfest.yml 0000664 0000000 0000000 00000000534 15015405444 0022564 0 ustar 00root root 0000000 0000000 name: Hacktoberfest
on:
schedule:
# Run every day in October
- cron: "0 0 * 10 *"
# Run on the 1st of November to revert
- cron: "0 13 1 11 *"
jobs:
hacktoberfest:
runs-on: ubuntu-latest
steps:
- uses: browniebroke/hacktoberfest-labeler-action@v2.3.0
with:
github_token: ${{ secrets.GH_PAT }}
bthome-ble-3.13.0/.github/workflows/issue-manager.yml 0000664 0000000 0000000 00000001340 15015405444 0022474 0 ustar 00root root 0000000 0000000 name: Issue Manager
on:
schedule:
- cron: "0 0 * * *"
issue_comment:
types:
- created
issues:
types:
- labeled
pull_request_target:
types:
- labeled
workflow_dispatch:
jobs:
issue-manager:
runs-on: ubuntu-latest
steps:
- uses: tiangolo/issue-manager@0.5.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >
{
"answered": {
"message": "Assuming the original issue was solved, it will be automatically closed now."
},
"waiting": {
"message": "Automatically closing. To re-open, please provide the additional information requested."
}
}
bthome-ble-3.13.0/.github/workflows/labels.yml 0000664 0000000 0000000 00000000775 15015405444 0021211 0 ustar 00root root 0000000 0000000 name: Sync Github labels
on:
push:
branches:
- main
paths:
- ".github/**"
jobs:
labels:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install labels
run: pip install labels
- name: Sync config with Github
run: labels -u ${{ github.repository_owner }} -t ${{ secrets.GITHUB_TOKEN }} sync -f .github/labels.toml
bthome-ble-3.13.0/.gitignore 0000664 0000000 0000000 00000004066 15015405444 0015614 0 ustar 00root root 0000000 0000000 # Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
bthome-ble-3.13.0/.gitpod.yml 0000664 0000000 0000000 00000000306 15015405444 0015704 0 ustar 00root root 0000000 0000000 tasks:
- command: |
pip install poetry
PIP_USER=false poetry install
- command: |
pip install pre-commit
pre-commit install
PIP_USER=false pre-commit install-hooks
bthome-ble-3.13.0/.idea/ 0000775 0000000 0000000 00000000000 15015405444 0014576 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/.idea/bthome-ble.iml 0000664 0000000 0000000 00000000515 15015405444 0017320 0 ustar 00root root 0000000 0000000
bthome-ble-3.13.0/.idea/watcherTasks.xml 0000664 0000000 0000000 00000005253 15015405444 0017770 0 ustar 00root root 0000000 0000000
bthome-ble-3.13.0/.idea/workspace.xml 0000664 0000000 0000000 00000002730 15015405444 0017320 0 ustar 00root root 0000000 0000000
bthome-ble-3.13.0/.pre-commit-config.yaml 0000664 0000000 0000000 00000003231 15015405444 0020076 0 ustar 00root root 0000000 0000000 # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
exclude: "CHANGELOG.md"
default_stages: [pre-commit]
ci:
autofix_commit_msg: "chore(pre-commit.ci): auto fixes"
autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate"
repos:
- repo: https://github.com/commitizen-tools/commitizen
rev: v4.8.2
hooks:
- id: commitizen
stages: [commit-msg]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: debug-statements
- id: check-builtin-literals
- id: check-case-conflict
- id: check-docstring-first
- id: check-json
- id: check-toml
- id: check-xml
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- id: debug-statements
# - repo: https://github.com/pre-commit/mirrors-prettier
# rev: v3.1.0
# hooks:
# - id: prettier
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
args: [--py311-plus]
- repo: https://github.com/PyCQA/isort
rev: 6.0.1
hooks:
- id: isort
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.1.0
hooks:
- id: black
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
hooks:
- id: codespell
- repo: https://github.com/PyCQA/flake8
rev: 7.2.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
- repo: https://github.com/PyCQA/bandit
rev: 1.8.3
hooks:
- id: bandit
args: [-x, tests]
bthome-ble-3.13.0/.readthedocs.yml 0000664 0000000 0000000 00000001004 15015405444 0016677 0 ustar 00root root 0000000 0000000 # Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
# Optionally declare the Python requirements required to build your docs
python:
install:
- method: pip
path: .
extra_requirements:
- docs
bthome-ble-3.13.0/CHANGELOG.md 0000664 0000000 0000000 00000110427 15015405444 0015434 0 ustar 00root root 0000000 0000000 # Changelog
## v3.13.0 (2025-05-27)
### Features
- Correct precipitation multiplier (#226) ([`c90145b`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c90145bd749b3dfe6adf27efda85d6aab92fc8a4))
### Chores
- Pre-commit autoupdate (#230) ([`e53f6d0`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e53f6d0b128cbbc63751f08df9d4616be1fba3c9))
- Bump bluetooth-sensor-state-data from 1.7.5 to 1.9.0 (#227) ([`ef62856`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ef628564d2039bf7e3e6d9ae80cc6e33f7708e43))
## v3.12.6 (2025-05-26)
### Bug fixes
- Update service info imports to use habluetooth (#229) ([`e9bf187`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e9bf187fb5597db1efc2dd647047b44ba47b853e))
### Chores
- Bump cryptography from 45.0.2 to 45.0.3 (#228) ([`8c1269a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8c1269aecae5d89517abb3730f5f80e904c91073))
- Pre-commit autoupdate (#225) ([`489e184`](https://github.com/Bluetooth-Devices/bthome-ble/commit/489e184933a617c99adb0c5572ffd24725096430))
- Bump habluetooth from 3.39.0 to 3.48.2 (#221) ([`3483e36`](https://github.com/Bluetooth-Devices/bthome-ble/commit/3483e36f9ba36d0db5c11c9787f1e3ce8a7a9009))
- Bump sphinx from 7.4.7 to 8.2.3 (#216) ([`8fcd6ac`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8fcd6ac7805a7069c436f528f128249e09d883e4))
- Bump cryptography from 44.0.3 to 45.0.2 (#224) ([`50684c1`](https://github.com/Bluetooth-Devices/bthome-ble/commit/50684c132ca79337e460838571185cd5a40233df))
- Pre-commit autoupdate (#223) ([`3701abe`](https://github.com/Bluetooth-Devices/bthome-ble/commit/3701abee343102b719c5511a3a12da59542a4672))
- Pre-commit autoupdate (#222) ([`e0db35d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e0db35daa5963a9ddee86702aa9eb00df770d5aa))
- Bump cryptography from 44.0.2 to 44.0.3 (#220) ([`26a4ca1`](https://github.com/Bluetooth-Devices/bthome-ble/commit/26a4ca1c192bcc8636d15616f708e11c34b195b1))
- Bump habluetooth from 3.38.0 to 3.39.0 (#219) ([`9d05b88`](https://github.com/Bluetooth-Devices/bthome-ble/commit/9d05b8874fce036fa32641b1dc4523baf2ac97bb))
- Bump pytest-cov from 6.1.0 to 6.1.1 (#218) ([`7de4cbb`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7de4cbbca3b3663b8c50d16235ebfebc1a39ea27))
- Bump bluetooth-data-tools from 1.26.5 to 1.28.1 (#217) ([`c869fe5`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c869fe5f2c443f54e947829303d14bf78795f785))
- Pre-commit autoupdate (#214) ([`8081aa0`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8081aa06008cf2b6d8161348e92fab22c9d9503e))
- Pre-commit autoupdate (#213) ([`68e9c2d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/68e9c2db77fb3f5c0b9e655a3cbbee2858e3025b))
## v3.12.5 (2025-04-03)
### Bug fixes
- Replace python-semantic-release/upload-to-gh-release with python-semantic-release/publish-action (#211) ([`412545f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/412545f38c2a847b6c0a71a950c18528406d097b))
### Chores
- Update deps (#212) ([`8b966ee`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8b966ee10444b9dffca84f32cf06e13dd824466e))
- Pre-commit autoupdate (#210) ([`05685d5`](https://github.com/Bluetooth-Devices/bthome-ble/commit/05685d50c755af654b54d26dfa33ef888244946a))
- Pre-commit autoupdate (#208) ([`ca34d81`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ca34d8172e5a7bd4de8642ba5a9f134e48be7e9b))
- Bump python-semantic-release/python-semantic-release from 9.17.0 to 9.21.0 in the github-actions group (#207) ([`8679b94`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8679b942a1ea188e0168194ee4bc32462e34f251))
- Pre-commit autoupdate (#206) ([`f011cb3`](https://github.com/Bluetooth-Devices/bthome-ble/commit/f011cb3d4ffb93dfbe25835ed7d9da6eb00082b7))
- Pre-commit autoupdate (#205) ([`d73e81a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d73e81a20624aaf825336aa65284c6b0efeb91af))
- Pre-commit autoupdate (#204) ([`031f55f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/031f55faf9b720094942bcf001f22f6706585ab7))
## v3.12.4 (2025-02-05)
### Bug fixes
- Update poetry to v2 (#203) ([`cd6cf77`](https://github.com/Bluetooth-Devices/bthome-ble/commit/cd6cf77f75db1b43a4e9fde215e9ee97000d8884))
### Chores
- Pre-commit autoupdate (#202) ([`c69055f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c69055fab999304246d29f565d435f241f846b6c))
- Bump bluetooth-data-tools from 1.22.0 to 1.23.3 (#200) ([`a8e2428`](https://github.com/Bluetooth-Devices/bthome-ble/commit/a8e242850636980f8f98190f19b3aa6260e6b18a))
- Bump myst-parser from 3.0.1 to 4.0.0 (#201) ([`167b88a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/167b88a1d4aeb54be0badfbc48aeda56b0387be8))
- Bump habluetooth from 3.17.0 to 3.21.0 (#199) ([`b64b747`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b64b74729ad31d85b10f92163a0f25cc8956be12))
## v3.12.3 (2025-01-31)
### Bug fixes
- Update pyproject to allow python 3.13 deps (#198) ([`4c4d9a6`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4c4d9a64be817fad5074aacd90d762e64f843845))
## v3.12.2 (2025-01-31)
### Bug fixes
- Use little endian in float conversion (#197) ([`ffd8f3c`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ffd8f3c6d959017bc662160cf345d9d49dd60f02))
## v3.12.1 (2025-01-31)
### Bug fixes
- Add changelog template (#196) ([`7ba1380`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7ba138083f8cbf578b7c947fdf0320b29e5173cb))
### Chores
- Bump the github-actions group across 1 directory with 7 updates (#195) ([`3bf607e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/3bf607e24585e262aa8bba4b071f47f4c18c4bc7))
- Bump habluetooth from 3.12.0 to 3.15.0 (#194) ([`e23edab`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e23edabef18641e4b6e2594ceeb451fb67c65b56))
- Bump sphinx from 6.2.1 to 7.4.7 (#193) ([`ebf9c3b`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ebf9c3bae5dbca4c5946a0bff541f40e3faa7a38))
- Update lint and commitlint (#190) ([`33b4e85`](https://github.com/Bluetooth-Devices/bthome-ble/commit/33b4e855e9e822da366175d1e1c2cbe4637bdc5a))
- Update dependabot.yml to include gha (#191) ([`e73268c`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e73268c84ce99fe8795ce38664242a4e3925eaa9))
### Unknown
## v3.12.0 (2025-01-31)
### Features
- Add direction and precipitation support (#187) ([`8c0df91`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8c0df91b7647b3fc2985edfd2382199f4d321665))
### Chores
- Pre-commit autoupdate (#185) ([`1e9f738`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1e9f738b457762e0e4d4eb232e73b077abe10c69))
- Bump myst-parser from 1.0.0 to 3.0.1 (#181) ([`8ce9658`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8ce9658095518e8fc0450377527e07e5813355ee))
- Bump habluetooth from 3.9.2 to 3.12.0 (#182) ([`59d6173`](https://github.com/Bluetooth-Devices/bthome-ble/commit/59d61738228bf7abac0b2e2f8d0081c981698412))
- Bump bluetooth-data-tools from 1.19.0 to 1.22.0 (#183) ([`39e3391`](https://github.com/Bluetooth-Devices/bthome-ble/commit/39e33911a4db85de1e1fe4e0423554395dbe067c))
- Bump sphinx-rtd-theme from 2.0.0 to 3.0.2 (#184) ([`0c756de`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0c756de2f7cf405b7bcdadd4745694274a133fce))
- Bump sphinx from 5.3.0 to 6.2.1 (#177) ([`38eea52`](https://github.com/Bluetooth-Devices/bthome-ble/commit/38eea524022fedbd86106625ce55d673b979f30c))
- Bump pytest-cov from 4.1.0 to 6.0.0 (#179) ([`a694d85`](https://github.com/Bluetooth-Devices/bthome-ble/commit/a694d851cfae703c61b03f4e49b67d7b544a8027))
- Bump habluetooth from 3.9.0 to 3.9.2 (#178) ([`ab590ff`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ab590ff022c3594d70d597eebfa1d5415a817970))
- Bump cryptography from 43.0.1 to 44.0.0 (#175) ([`ebd8e9e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ebd8e9e4d8140c4ecbc43ac0cc2cbffd1d01c2f0))
- Bump pytest from 7.4.4 to 8.3.4 (#176) ([`07e6dad`](https://github.com/Bluetooth-Devices/bthome-ble/commit/07e6dadb58c9f45aaf1f419adcef3f9932274ffa))
- Bump jinja2 from 3.1.4 to 3.1.5 (#172) ([`4fcc1a1`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4fcc1a1aa8d349e614934580f7a631dc12886095))
- Bump cryptography from 42.0.8 to 43.0.1 (#171) ([`bdd449b`](https://github.com/Bluetooth-Devices/bthome-ble/commit/bdd449b68b1865ead298f5a105d53f6afcf51855))
- Bump certifi from 2024.6.2 to 2024.7.4 (#173) ([`4bf94dc`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4bf94dcda236145bfdf30ee468e69459ea4ccc72))
- Bump urllib3 from 2.2.1 to 2.2.2 (#174) ([`77e5493`](https://github.com/Bluetooth-Devices/bthome-ble/commit/77e5493249efe6bb552f8ad98722afcff3d7cbf4))
- Bump myst-parser from 0.18.1 to 1.0.0 (#168) ([`fa3964d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/fa3964df716baa1f1840b96cf20479e474a1a9d5))
- Bump bluetooth-sensor-state-data from 1.7.0 to 1.7.1 (#166) ([`d1a0f3a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d1a0f3aac30eab6f8ba4c27f5d371f00aa24355f))
- Update for python 3.13 (#165) ([`8361b69`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8361b6901f30066634f2a32c987affd42b32b832))
- Bump sensor-state-data from 2.18.0 to 2.18.1 (#167) ([`4522dc8`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4522dc8efd66eb4ed2f259f1f9a3ee071d55538d))
- Bump sphinx-rtd-theme from 1.3.0 to 2.0.0 (#169) ([`481ff63`](https://github.com/Bluetooth-Devices/bthome-ble/commit/481ff63bde979f7070755bb2d7174b5bcf4da940))
- Bump habluetooth from 3.1.1 to 3.9.0 (#170) ([`9d6d360`](https://github.com/Bluetooth-Devices/bthome-ble/commit/9d6d36030281fa4ba6fc3764775663c21b9e52de))
- Create dependabot.yml ([`a2988df`](https://github.com/Bluetooth-Devices/bthome-ble/commit/a2988df1f355e887ff4b4a95dccd4b06f92b2200))
- Pre-commit autoupdate (#164) ([`0d22035`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0d2203590b4d524e27153351e8ad3e148b8490cb))
- Pre-commit autoupdate (#163) ([`ff18462`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ff184624bed27311394863640643a491e25b715c))
- Pre-commit autoupdate (#160) ([`da60d99`](https://github.com/Bluetooth-Devices/bthome-ble/commit/da60d994355f8c0b2715753600fa791a46a6eb2d))
- Pre-commit autoupdate (#158) ([`c07015a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c07015aa64b1d6997fca27871a6a19060d66a1c5))
- Pre-commit autoupdate (#157) ([`354359a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/354359a0b59c4bd7c6c621341c089238f8a03993))
- Pre-commit autoupdate (#155) ([`f1216c3`](https://github.com/Bluetooth-Devices/bthome-ble/commit/f1216c3e338a67663d1436207c9db16e38f23d18))
- Pre-commit autoupdate (#154) ([`6f550fa`](https://github.com/Bluetooth-Devices/bthome-ble/commit/6f550fae80fc75677c2adfed57d04d5b2d6e9966))
- Pre-commit autoupdate (#151) ([`c165c5e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c165c5e25ee554ce631227c744a3ceb7d5665b5c))
- Pre-commit autoupdate (#150) ([`ef50709`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ef50709e00d0b6506ddda30f5eb203f63e012bae))
- Add ohf logo to readme (#149) ([`5cc4215`](https://github.com/Bluetooth-Devices/bthome-ble/commit/5cc42155a7e90b0ce73c05312304801554a02212))
- Pre-commit autoupdate (#145) ([`e1b850d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e1b850dcc1c68ffb10179a6796c7bd4f9d121eeb))
### Unknown
## v3.11.0 (2024-08-25)
### Features
- Add new sensors (#144) ([`25e87a0`](https://github.com/Bluetooth-Devices/bthome-ble/commit/25e87a0e2d7bb0109e148046ed09235a0d3fa596))
## v3.10.0 (2024-08-23)
### Features
- Add 0x56 for 16-bit conductivity measurement type (#142) ([`444eaca`](https://github.com/Bluetooth-Devices/bthome-ble/commit/444eacafb280fffe8ee28abc4e9236b84db15bf6))
### Chores
- Pre-commit autoupdate (#140) ([`07f4729`](https://github.com/Bluetooth-Devices/bthome-ble/commit/07f4729813086007e04a7c7dd77e7dc240433565))
- Pre-commit autoupdate (#139) ([`e6ab18b`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e6ab18b702735465207df952af1273762081d8e1))
- Pre-commit autoupdate (#137) ([`8305ab4`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8305ab41118aee608b6d5f63453bf1d76dbcae61))
- Pre-commit autoupdate (#136) ([`8de8a22`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8de8a22a154f2228161fdd6d1c9a861004a18148))
- Pre-commit autoupdate (#132) ([`8171fd4`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8171fd41955a8a9a89094c235f8213af1a6b2014))
## v3.9.3 (2024-06-07)
### Bug fixes
- Allow contents permission to workflow (#131) ([`eda9bfd`](https://github.com/Bluetooth-Devices/bthome-ble/commit/eda9bfd454af3a0fd58d11fd951bebc28374e89d))
- Rename pypi token (#130) ([`03a9d90`](https://github.com/Bluetooth-Devices/bthome-ble/commit/03a9d90c33582b773f0737daf95495b9d123bafd))
- Use list for version_toml (#129) ([`0fc3d45`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0fc3d453d8b31723668daf6720c80adf71821b9c))
- Reference to gh action pypi push (#128) ([`88b1fe9`](https://github.com/Bluetooth-Devices/bthome-ble/commit/88b1fe968ee3f9e62bf2e1c49d445db079013235))
- Fix pypi workflow (#127) ([`53c8e0d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/53c8e0dc34af2ef86f4fbc67617dc35a3d951962))
- Fix release workflow (#126) ([`4d985e9`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4d985e91edfb01be2df83b6074758e88b288da39))
- Bump python-semantic-release (#124) ([`bbef3a7`](https://github.com/Bluetooth-Devices/bthome-ble/commit/bbef3a7f7ba4b77949a60cbaba6d2e64889e8b1b))
## v3.9.2 (2024-06-07)
### Bug fixes
- Make tests compatible with habluetooth>=3.0 (#123) ([`92fceeb`](https://github.com/Bluetooth-Devices/bthome-ble/commit/92fceebf97add809f883039d9954f35f61a17019))
## v3.9.1 (2024-06-05)
### Bug fixes
- Accept new encryption counter only if the message was not corrupted (#119) ([`bc61824`](https://github.com/Bluetooth-Devices/bthome-ble/commit/bc61824bfe6c1d3c7394e7754f757a0241c14939))
## v3.9.0 (2024-05-07)
### Features
- Add button hold event (#120) ([`49124ab`](https://github.com/Bluetooth-Devices/bthome-ble/commit/49124ab9db18e179aba9fb731ba4ed08e113eff3))
## v3.8.1 (2024-03-18)
### Bug fixes
- Remove mac workaround (#115) ([`303f833`](https://github.com/Bluetooth-Devices/bthome-ble/commit/303f8331dac612f848c875869284be0ca621692e))
## v3.8.0 (2024-03-10)
### Features
- Allow one failed decryption before reauth (#114) ([`7c499cc`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7c499cce6c15ab6f21b25779bae773e3c00f7841))
## v3.7.0 (2024-03-08)
### Features
- Add device title to log messages (#113) ([`c47f36f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c47f36f17698ad48a94db3501a37ae9421c7ff7e))
## v3.6.0 (2024-03-02)
### Features
- Verify packet is not an older packet (#112) ([`113e49d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/113e49df1460ea407202e5ca93ff170d6b813b75))
## v3.5.0 (2024-01-18)
### Features
- Add verification of the packet id (#105) ([`c428f92`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c428f927b8dbc4f02ef006c15932aca5fa74877f))
## v3.4.2 (2024-01-17)
### Bug fixes
- Fix counter verification (#104) ([`dead672`](https://github.com/Bluetooth-Devices/bthome-ble/commit/dead67297a2a5662d6a9b12fcc8e671ae1d833a1))
## v3.4.1 (2024-01-10)
### Bug fixes
- Use volume storage instead of water storage (#102) ([`4cf20bf`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4cf20bfd027286ba7ca842125d3cafbc0797e6c1))
## v3.4.0 (2024-01-10)
### Features
- Add water storage sensor (#101) ([`6eb80e0`](https://github.com/Bluetooth-Devices/bthome-ble/commit/6eb80e0e120f7c8ef6ce4b7ee5057ae5356192b3))
## v3.3.1 (2023-12-15)
### Bug fixes
- No check of encryption counter when bindkey has not been verified (#97) ([`43f1412`](https://github.com/Bluetooth-Devices/bthome-ble/commit/43f14123b68732f41552d2f198dbbf2323763614))
## v3.3.0 (2023-12-13)
### Features
- Add check for increasing encryption counter (#92) ([`a58cf7b`](https://github.com/Bluetooth-Devices/bthome-ble/commit/a58cf7bf84bacaf0b8436f03499710d5b83e3add))
## v3.2.0 (2023-10-01)
### Features
- Add raw hex sensor (#91) ([`6f9969e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/6f9969e73efe0ccaa810d3e7aa136af673eca3c2))
### Refactoring
- Remove dependency on pytz (#90) ([`4619caa`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4619caa03eab37c7e992b1cf8caaf253818ff816))
## v3.1.1 (2023-08-20)
### Bug fixes
- Replace datetime.utcfromtimestamp (#87) ([`0184fe7`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0184fe7b04caabc45fe3e8da1191269a7d19b975))
## v3.1.0 (2023-08-13)
### Features
- Add text sensor (#86) ([`2bfcd9b`](https://github.com/Bluetooth-Devices/bthome-ble/commit/2bfcd9b9b4a2321eb229298258235b61a97ae9d2))
## v3.0.0 (2023-07-15)
### Features
- Add ability to set the bind key after init (#83) ([`1826139`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1826139cc81bbdb0b9fdd616b16fa92b07654f6b))
## v2.13.0 (2023-07-15)
### Unknown
### Bug fixes
- Lint ([`576342d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/576342d480f7a2f01337e96141dc4db2d896a82e))
### Features
- Switch from pycryptodomex to cryptography ([`0cdc8e6`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0cdc8e674bc5f6d6f8cef64891d30c1e90f415ae))
## v2.12.2 (2023-07-15)
### Unknown
### Bug fixes
- Decryption bug fixed ([`dfb12e6`](https://github.com/Bluetooth-Devices/bthome-ble/commit/dfb12e6d46e29cadaff39b5e9d6625a708502d05))
## v2.12.1 (2023-07-07)
### Unknown
### Chores
- Bump bluetooth-sensor-state-data ([`54556a3`](https://github.com/Bluetooth-Devices/bthome-ble/commit/54556a35529d37a07420623f6c4b4911c49a33d5))
### Bug fixes
- Bump sensor-state-data to 2.16.1 ([`9657a91`](https://github.com/Bluetooth-Devices/bthome-ble/commit/9657a91bd39550b74fe3d822c1e47b407aa25131))
- Fix for repeated events ([`285da3c`](https://github.com/Bluetooth-Devices/bthome-ble/commit/285da3c76e719043295225f81b9c96279ea7554c))
## v2.12.0 (2023-06-18)
### Unknown
### Bug fixes
- Bump semantic release ([`ebe3484`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ebe34848db5d3a88ac2023cb26bf2803e51b88fd))
- Fix failing tests ([`3cf0634`](https://github.com/Bluetooth-Devices/bthome-ble/commit/3cf0634cf2eb91b55990746f161d9605e00d0fde))
- Use utcfromtimestamp ([`1fea0eb`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1fea0eb3a257c5c4e8070fa40ee221a5a297ef86))
### Features
- Timestamp acceleration and gyroscope ([`07c6b40`](https://github.com/Bluetooth-Devices/bthome-ble/commit/07c6b4023dd3e960ab16bb80a5565f91738b55ad))
## v2.11.3 (2023-05-19)
### Unknown
### Bug fixes
- Revert trigger based device name ([`f2b5303`](https://github.com/Bluetooth-Devices/bthome-ble/commit/f2b5303952d5e4ec1ac63307ba43aca7c9262a36))
## v2.11.2 (2023-05-12)
### Unknown
### Bug fixes
- Pre-commit bump and bump release ([`97540cc`](https://github.com/Bluetooth-Devices/bthome-ble/commit/97540cccf52e6c49357082917228a1b704579ba0))
## v2.11.1 (2023-05-12)
### Unknown
### Bug fixes
- Add trigger based device to device type ([`0d0076b`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0d0076ba6ee54fd1d68a67453c549f27fc9e5a2d))
## v2.11.0 (2023-05-04)
### Unknown
### Bug fixes
- Clean code ([`26bc5d2`](https://github.com/Bluetooth-Devices/bthome-ble/commit/26bc5d2ebd2e840c2a32b140275c8eee7c2f79e8))
### Features
- Add sleepy device bit ([`d61e63e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d61e63ebd68461fae26ef08b05c5d5771dae45fb))
## v2.10.1 (2023-04-28)
### Unknown
### Bug fixes
- Add sleepy sensors to init ([`7a82a50`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7a82a50f2be23e3c429626fc98f03a6ab80ed7bb))
## v2.10.0 (2023-04-28)
### Unknown
### Bug fixes
- Typo ([`7ddb135`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7ddb1357421e9f419529955e05cb98915539aeab))
### Features
- Add sleepy devices ([`2bcbf38`](https://github.com/Bluetooth-Devices/bthome-ble/commit/2bcbf38e51bb6d1a0791682acceacb721edfc866))
### Chores
- Add tests for shelly button ([`c8c8365`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c8c83654f7493e647fbb14d12d1715497dde51dc))
## v2.9.0 (2023-03-12)
### Unknown
### Features
- Add water sensor ([`c228d77`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c228d772a4c2757e59697b9b233057d1648f9525))
## v2.8.0 (2023-03-05)
### Unknown
### Features
- Add gas water energy meter ([`152bcea`](https://github.com/Bluetooth-Devices/bthome-ble/commit/152bcea19394fa32b82fca1d2cfffcba5609d81c))
## v2.7.0 (2023-02-25)
### Unknown
### Features
- Add gas sensor ([`956610f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/956610fd72e5490f0dbf47ab5cbac7871c8d2565))
## v2.6.0 (2023-02-17)
### Unknown
### Bug fixes
- Fix flake 8 line length error ([`e7490b4`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e7490b487201abf74c6e7271cee1d9eab1db2c2c))
### Features
- Add possibility to include mac in payload ([`1059aa1`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1059aa1eca2fc7ac7518e0a9dbbb822548417bf0))
## v2.5.2 (2023-02-10)
### Bug fixes
- Parse objects of the same time correctly (#47) ([`27e5982`](https://github.com/Bluetooth-Devices/bthome-ble/commit/27e5982214ee34cca6243532dff4e9b2e65e0da9))
## v2.5.1 (2023-01-24)
### Unknown
### Bug fixes
- Correct data length check ([`aed08c4`](https://github.com/Bluetooth-Devices/bthome-ble/commit/aed08c4d07a7fc1dd25d5f081a88d88171946498))
## v2.5.0 (2023-01-11)
### Unknown
### Features
- Add voltage with 1 digit ([`acd1da7`](https://github.com/Bluetooth-Devices/bthome-ble/commit/acd1da7e62a652cba588d8b543479afd6b86a42f))
## v2.4.1 (2023-01-04)
### Unknown
### Bug fixes
- Bump sensor-state-data ([`1202ace`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1202ace26e0a5acdb26cc90e4eae1d8deab3a2dd))
- Bump sensor-state-data ([`7bff7be`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7bff7be46ad1c0ca082c7801f7741343c92cb342))
- Bump sensor-state-data ([`930b222`](https://github.com/Bluetooth-Devices/bthome-ble/commit/930b22299e20c61bd8256b4a9db3b4306e4a224a))
## v2.4.0 (2022-12-17)
### Unknown
### Bug fixes
- Update tests and dependencies ([`bb9d0a2`](https://github.com/Bluetooth-Devices/bthome-ble/commit/bb9d0a2c4941915dd9e37b8fe8db3ef5f8c627d3))
### Features
- Add volume and packet id ([`aaefa4e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/aaefa4e5913bc6db80ad98e4e2947fb58e8abb83))
## v2.3.1 (2022-11-19)
### Unknown
### Bug fixes
- Warning for numerical order object id ([`b86feaa`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b86feaa7f2845c1471bd13d48443a23122078bc6))
## v2.3.0 (2022-11-18)
### Unknown
### Features
- Remove cipher update ([`21b9390`](https://github.com/Bluetooth-Devices/bthome-ble/commit/21b9390953341e540a42636f49b09ab17519ef5c))
## v2.2.1 (2022-11-07)
### Unknown
### Bug fixes
- Multiple measurements fix ([`849deab`](https://github.com/Bluetooth-Devices/bthome-ble/commit/849deab688a83ba040e8f7e42598b4aa339d0460))
## v2.2.0 (2022-11-06)
### Unknown
### Bug fixes
- Add tests for duration and temperature ([`552cbb8`](https://github.com/Bluetooth-Devices/bthome-ble/commit/552cbb8be03e62b1b9e2231ba4c658c658d375cd))
- Uv and duration sensors ([`4130c71`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4130c71b01f4a1d733e11b153fbe50031b53b48b))
### Features
- Update dependencies ([`a3e9daf`](https://github.com/Bluetooth-Devices/bthome-ble/commit/a3e9dafcaa907ae3672c60df7e2da3ce8128b340))
## v2.1.0 (2022-11-03)
### Unknown
### Bug fixes
- Update poetry file ([`90cf1ea`](https://github.com/Bluetooth-Devices/bthome-ble/commit/90cf1eaf4e8b474dfda13c6f579a46a3f08e95d2))
### Features
- New sensor types ([`1bbf778`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1bbf778887cb411d710a7787c3ae0f68a9a9eac2))
## v2.0.0 (2022-11-01)
### Features
- V2 release bump ([`93bfd22`](https://github.com/Bluetooth-Devices/bthome-ble/commit/93bfd22eff117f1cf2b98248b2ccf9b9dd90e6d8))
- V2 release bump ([`3487df3`](https://github.com/Bluetooth-Devices/bthome-ble/commit/3487df36be7a478d5b5512f348ade6cb43a67079))
### Unknown
## v1.4.0 (2022-11-01)
### Unknown
### Bug fixes
- Parser not parsing multiple uuids (#21) ([`e8d2646`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e8d2646bb73d39e7920a04243d862c9ed68ed2c0))
- Adjust button events ([`b796cdb`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b796cdb9d18703c12bfbc1a03602edae823df6f0))
- Logs and fix for wrong id ([`fc611c9`](https://github.com/Bluetooth-Devices/bthome-ble/commit/fc611c940295f7254bb89911d450e3abee544880))
- Fix v1 tests ([`8099cad`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8099cadcf47efdfa10a4945b8cad6cbd9fad88f6))
- Add annotations ([`fb19043`](https://github.com/Bluetooth-Devices/bthome-ble/commit/fb19043808124ba9cfcea340c6144c9d19abc84a))
- Button and dimmer events ([`ac3b8fa`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ac3b8faac93913cf93f2095053dee580407ae939))
- Wrong bthome device info byte ([`97d03ef`](https://github.com/Bluetooth-Devices/bthome-ble/commit/97d03efe657d875c06b6018453cc692a73a14531))
- Remove device_info_flag ([`f524294`](https://github.com/Bluetooth-Devices/bthome-ble/commit/f524294f29ad3df43066c5e13faf90c45e1e9b7d))
- Resolve comments ([`5a904f6`](https://github.com/Bluetooth-Devices/bthome-ble/commit/5a904f6c97c2ad17ad706c224dbc5c2b2265f7b3))
- Improve coverage ([`f21a739`](https://github.com/Bluetooth-Devices/bthome-ble/commit/f21a739325ed2fe20d22256fe6e085c6404f8b12))
- Resolve comments review ([`e0d58a0`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e0d58a0e139f1b7928ff3407a56c02e40df9c5da))
- Remove mac parser and code cleaning ([`4a4fe68`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4a4fe683b781caf05524f6a38290d8215bb3a4b6))
### Features
- Remove object format and length byte ([`2f43a29`](https://github.com/Bluetooth-Devices/bthome-ble/commit/2f43a296cfb59fcb2938502801b3f4d12785358c))
- Remove predefined device info ([`cb16b35`](https://github.com/Bluetooth-Devices/bthome-ble/commit/cb16b35edec71c609ae12281f900f6f56d300e93))
- Multiple measurements of the same type ([`e57ed3d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e57ed3d857164d373bba3ca86f368a9a4ed9030f))
- Uuid v2 and adv_info byte ([`a4d6440`](https://github.com/Bluetooth-Devices/bthome-ble/commit/a4d64403dd359102762f2dbea777ccdf5e58a21d))
## v1.3.0 (2022-10-04)
### Features
- Force new release adding events ([`8ae802a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8ae802a6a34eb9419504347a5a498bf9617063d1))
### Unknown
## v1.2.3 (2022-09-29)
### Unknown
### Bug fixes
- Add missing comma ([`0f889e6`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0f889e62b83247e7d0bb6f7edfeb695691a5f782))
- Remove unused imports ([`8f45606`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8f45606d106f7cd43a1875c8d36226ab73745ede))
- Add test for encryption example ([`c323a31`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c323a31355592947f4c00cb0b6061f175f3a0686))
- Fix formatting issues ([`47017b6`](https://github.com/Bluetooth-Devices/bthome-ble/commit/47017b6ed584a49a4ee2aa85ee678f3de2cd120b))
- Fix annotation ([`1bc8f72`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1bc8f72de8d807c333dc8ba767c5a0c2043dc4b1))
- Fix formatting issues ([`801679e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/801679eda2524f01d48e76cab6bf5310ade682ec))
- Remove use of predefined sensor wrapper ([`377adaa`](https://github.com/Bluetooth-Devices/bthome-ble/commit/377adaa576b03a76340570729dfd4ac2bf019b36))
- Remove unused lists ([`fd08fae`](https://github.com/Bluetooth-Devices/bthome-ble/commit/fd08faee434bc3b66aec241627407edc3eab4d80))
## v1.2.2 (2022-09-14)
### Unknown
### Bug fixes
- Remove update binary sensor ([`e529f89`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e529f899eb7b9f38b65c36f5cc46d3df41e91f75))
## v1.2.1 (2022-09-13)
### Unknown
### Bug fixes
- Remove empty line ([`5f7ba24`](https://github.com/Bluetooth-Devices/bthome-ble/commit/5f7ba2438f4a8e6bde341b74668514906364d070))
- Sort import order ([`ba5afd1`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ba5afd11bea7678f0a9af50ad9c4a08a0800b62c))
- Always use bthome device classes ([`15fa937`](https://github.com/Bluetooth-Devices/bthome-ble/commit/15fa93718d7b506cc25e80831f7612423bd13306))
## v1.2.0 (2022-09-09)
### Unknown
### Bug fixes
- Update poetry lock file ([`477f974`](https://github.com/Bluetooth-Devices/bthome-ble/commit/477f974aba5ef9d469975473902994d01ed00181))
## v1.1.1 (2022-09-08)
### Bug fixes
- Flake8 error ([`4ed3312`](https://github.com/Bluetooth-Devices/bthome-ble/commit/4ed3312453c35d1911bb463a2c0b35310626e84f))
- Remove unused import ([`44f171a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/44f171aad9375877dfba4f55d6f6f2ede12ccdad))
- Mypy error ([`716f626`](https://github.com/Bluetooth-Devices/bthome-ble/commit/716f626729080c451c24dd7da242d97709d620a7))
- Add binary sensor device class ([`d48783e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d48783e1a7c3a3a719fc7dca9f08feb034660507))
### Features
- Add binary sensors ([`c83d0da`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c83d0dab1aa44730976c0185c73d928a72a01b75))
## v1.1.0 (2022-09-06)
### Unknown
### Bug fixes
- Fix tests ([`ffce92d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ffce92d599d1831ae241e10bddb874b5d5761a15))
- Fix flake8 tests ([`b428339`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b428339e38f28caf17adc02b48a9585f05b629d1))
### Features
- Binary sensor support ([`35ec5e1`](https://github.com/Bluetooth-Devices/bthome-ble/commit/35ec5e1d328038b328c4b793e9fe70c354d731dd))
## v1.0.0 (2022-09-01)
### Unknown
### Features
- Change name of bthome with capital h ([`cf3c702`](https://github.com/Bluetooth-Devices/bthome-ble/commit/cf3c702ca4b2e25cc15a74379deeb84a949427d7))
- Bthome with capital h ([`c307462`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c3074628da771ce78810538e8776797aae4df147))
## v0.5.2 (2022-08-30)
### Bug fixes
- Use full name for bparasite ([`bcd07da`](https://github.com/Bluetooth-Devices/bthome-ble/commit/bcd07da262009383f4e67592fec6614937fe847f))
## v0.5.1 (2022-08-29)
### Bug fixes
- Fix tests for non standard device classes ([`d48ec31`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d48ec31005c35d8f064bf2717c648b058bb1b468))
- Use update_sonsor for other device classes ([`eabde87`](https://github.com/Bluetooth-Devices/bthome-ble/commit/eabde8776ac6cdf9e7d1753aa4b2909d769de37d))
## v0.5.0 (2022-08-28)
### Unknown
### Bug fixes
- Add test for b-parasite ([`ecbb823`](https://github.com/Bluetooth-Devices/bthome-ble/commit/ecbb823ce604903a391df8c1e3a4f4d1558abc74))
- Bump sensor-state-data ([`e3e21af`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e3e21af1a7df36891aa504891afdab9caef82b06))
- Lint errors ([`8c3697e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8c3697e479f20ce41763fbd32c9872af6974d990))
### Features
- Add new sensor types ([`15ac53a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/15ac53af864ba3446973a4518f6dd30509bc1347))
## v0.4.0 (2022-08-26)
### Features
- Get manufacturer from name ([`1e66c90`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1e66c90cbe884852dab74c57708ba702ecafc736))
## v0.3.8 (2022-08-25)
### Bug fixes
- Failing reauth ([`7b3b0d2`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7b3b0d2c66546c1b2d55d42ec95d94a0d7117ab5))
## v0.3.7 (2022-08-25)
### Unknown
### Bug fixes
- Code format ([`f5c531f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/f5c531f73ea4be7dd749f0301bb1e486a0493106))
- Use short address from data tools ([`722a97f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/722a97f513b7c6db63173433dea629ba59445fda))
## v0.3.6 (2022-08-24)
### Bug fixes
- Flake8 error ([`cc9f077`](https://github.com/Bluetooth-Devices/bthome-ble/commit/cc9f077ab9ce3685305a2407b17150a6e7f9bac5))
- Table format ([`eca5d42`](https://github.com/Bluetooth-Devices/bthome-ble/commit/eca5d42e710c5edadc190f289d88219f43699ee6))
- Units of voc ([`0f4c773`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0f4c773c02310a3546c2687cf5d18d31677c0549))
## v0.3.5 (2022-08-24)
### Bug fixes
- Workaround for empty service_uuids ([`2cab081`](https://github.com/Bluetooth-Devices/bthome-ble/commit/2cab081c591de7807c02424819bfca243d360b69))
## v0.3.4 (2022-08-23)
### Unknown
### Bug fixes
- Length check ([`9817c84`](https://github.com/Bluetooth-Devices/bthome-ble/commit/9817c84f78993c743c354a09789354c6fa600a18))
## v0.3.3 (2022-08-23)
### Bug fixes
- Minor change to force a new release ([`a55133a`](https://github.com/Bluetooth-Devices/bthome-ble/commit/a55133a243c3b40b4a1a330a70f4aa8930d5dc8a))
### Unknown
## v0.3.2 (2022-08-21)
### Unknown
## v0.3.1 (2022-08-21)
### Bug fixes
- Supported sensor fix ([`0bc45bf`](https://github.com/Bluetooth-Devices/bthome-ble/commit/0bc45bf822da65d705823daff57881c917b9c8fc))
### Unknown
## v0.3.0 (2022-08-19)
### Bug fixes
- Remove double mac from name ([`eb77ad8`](https://github.com/Bluetooth-Devices/bthome-ble/commit/eb77ad8d947d53bfc585515cc512d088913f69e1))
- Isort ([`d85e345`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d85e345ddfc4380c317df83774ef9fe61594d037))
### Features
- Add encryption support ([`e299228`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e299228e02313c154027bdef017a64db5d7fbe4c))
## v0.2.2 (2022-08-18)
### Unknown
## v0.2.1 (2022-08-18)
### Bug fixes
- Auto release test ([`8fa2ba6`](https://github.com/Bluetooth-Devices/bthome-ble/commit/8fa2ba6831e793652d0658292730942345585bf5))
- Auto release ([`1eac241`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1eac2415693e1a60d948e023fce5b8db108d56c1))
## v0.2.0 (2022-08-18)
### Unknown
### Bug fixes
- Delete poetry log ([`1376c47`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1376c473aa5f9c3783f9f2b03dfccf483f224335))
- Update sensor-state-data version ([`132cf9e`](https://github.com/Bluetooth-Devices/bthome-ble/commit/132cf9e74560a2abd5e263ddda04c70c9a6a1cfd))
### Features
- Update dependency ([`b624ef5`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b624ef51b40f371eb23dfe2a66b2ecc1752832fd))
- Update dependencies ([`b0177f4`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b0177f4427464e54262e1ca087d94bad570087a7))
## v0.1.0 (2022-08-18)
### Bug fixes
- Increase line length ([`1dd1baa`](https://github.com/Bluetooth-Devices/bthome-ble/commit/1dd1baa45549b2739ca0e7d787046cf2dfc637d4))
- Increase line length ([`c8d9a56`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c8d9a56a9d61390b9617b7ec0a8ea83d267615d6))
- Flake 8 errors ([`71fae3c`](https://github.com/Bluetooth-Devices/bthome-ble/commit/71fae3c1c008874e8c5af492b714c035c7e1a872))
- Flake8 errors ([`dd7a283`](https://github.com/Bluetooth-Devices/bthome-ble/commit/dd7a283582b48556a9212f3702d287111a3847b3))
- Flake8 errors ([`b36bb2d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b36bb2d4c79ba1823d290449a65965ee01145dee))
- Mypy error ([`1364971`](https://github.com/Bluetooth-Devices/bthome-ble/commit/13649712943bfacacf6e3e2e53a2ea677073962e))
- Formatting conflicts ([`e2548b0`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e2548b00485e3485ee91715b5229a60b4104aa01))
- Formatting errors ([`7bef4a3`](https://github.com/Bluetooth-Devices/bthome-ble/commit/7bef4a34beb38d94bc023aefc964a8945e43ff6b))
- Flake8 errors ([`8634293`](https://github.com/Bluetooth-Devices/bthome-ble/commit/863429339ad6e1f1b8a75b0bc06283979110783a))
- Bump sensor-state-data ([`d9e409d`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d9e409dbb5612fba786338f35a4354d1e1a0463c))
- Linting errors ([`b1dc181`](https://github.com/Bluetooth-Devices/bthome-ble/commit/b1dc1815cf49100b94f99430948757f0790a5a04))
- Linting errors ([`d47bfc9`](https://github.com/Bluetooth-Devices/bthome-ble/commit/d47bfc99223a7bb5237db7ced36b3e73992b599e))
- Linting errors ([`e33c68f`](https://github.com/Bluetooth-Devices/bthome-ble/commit/e33c68f7698a3ff5ee1ac9bc3bcb408d9458aae5))
### Features
- Initial release ([`c097555`](https://github.com/Bluetooth-Devices/bthome-ble/commit/c0975559955bddab4c96aa993e381107b4f7c152))
### Unknown
bthome-ble-3.13.0/CONTRIBUTING.md 0000664 0000000 0000000 00000010370 15015405444 0016050 0 ustar 00root root 0000000 0000000 # Contributing
Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given.
You can contribute in many ways:
## Types of Contributions
### Report Bugs
Report bugs to [our issue page][gh-issues] for code issues or feature requests. If you are reporting a bug, please include:
- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.
Documentation issues of the website [bthome.io](https://bthome.io) can be reported at the [issue page of the website][doc-issues].
### Fix Bugs
Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it.
### Implement Features
Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it.
### Write Documentation
BTHome BLE could always use more documentation, whether as part of the official BTHome BLE docs, in docstrings, or even on the web in blog posts, articles, and such.
The official website is located at [bthome.io](https://bthome.io). Changes to the website can be done on the [GitHub page of the site](https://github.com/home-assistant/bthome.io).
### Submit Feedback
The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that contributions are welcome 😊
## Get Started!
Ready to contribute? Here's how to set yourself up for local development.
1. Fork the repo on GitHub.
2. Clone your fork locally:
```shell
$ git clone git@github.com:your_name_here/bthome-ble.git
```
3. Go to the cloned folder:
```shell
$ cd bthome-ble
```
4. Install the project dependencies with [Poetry](https://python-poetry.org):
```shell
$ poetry install
```
5. Create a branch for local development:
```shell
$ git checkout -b name-of-your-bugfix-or-feature
```
Now you can make your changes locally.
6. When you're done making changes, check that your changes pass our tests:
```shell
$ poetry run pytest
```
7. 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:
```shell
$ pre-commit run -a
```
Or better, install the hooks once and have them run automatically each time you commit:
```shell
$ pre-commit install
```
8. Commit your changes and push your branch to GitHub:
```shell
$ git add .
$ git commit -m "feat(something): your detailed description of your changes"
$ git push origin name-of-your-bugfix-or-feature
```
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.
9. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed):
```shell
$ gh pr create --fill
```
## Pull Request Guidelines
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:
1. Include tests for feature or bug fixes.
2. Update the documentation for significant features.
3. Ensure tests are passing on CI.
## Tips
To run a subset of tests:
```shell
$ pytest tests
```
## Making a new release
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.
[gh-issues]: https://github.com/bluetooth-devices/bthome-ble/issues
[doc-issues]: https://github.com/home-assistant/bthome.io/issues
bthome-ble-3.13.0/LICENSE 0000664 0000000 0000000 00000002056 15015405444 0014626 0 ustar 00root root 0000000 0000000
MIT License
Copyright (c) 2022 Ernst Klamer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
bthome-ble-3.13.0/README.md 0000664 0000000 0000000 00000007366 15015405444 0015111 0 ustar 00root root 0000000 0000000 # BTHome BLE
BLE parser for sensors that support the BTHome BLE format (V1 and V2).
## Installation
Install this via pip (or your favourite package manager):
`pip install bthome-ble`
## BThome BLE format
More detailed information about the BTHome BLE format can be found on the [usage page](https://bthome.io/)
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
## Credits
This package was created with
[Cookiecutter](https://github.com/audreyr/cookiecutter) and the
[browniebroke/cookiecutter-pypackage](https://github.com/browniebroke/cookiecutter-pypackage)
project template.
[](https://www.openhomefoundation.org/)
bthome-ble-3.13.0/commitlint.config.mjs 0000664 0000000 0000000 00000000362 15015405444 0017755 0 ustar 00root root 0000000 0000000 export default {
extends: ["@commitlint/config-conventional"],
rules: {
"header-max-length": [0, "always", Infinity],
"body-max-line-length": [0, "always", Infinity],
"footer-max-line-length": [0, "always", Infinity],
},
};
bthome-ble-3.13.0/docs/ 0000775 0000000 0000000 00000000000 15015405444 0014546 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/docs/Makefile 0000664 0000000 0000000 00000001175 15015405444 0016212 0 ustar 00root root 0000000 0000000 # Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
bthome-ble-3.13.0/docs/make.bat 0000664 0000000 0000000 00000001374 15015405444 0016160 0 ustar 00root root 0000000 0000000 @ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
bthome-ble-3.13.0/docs/source/ 0000775 0000000 0000000 00000000000 15015405444 0016046 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/docs/source/_static/ 0000775 0000000 0000000 00000000000 15015405444 0017474 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/docs/source/_static/.gitkeep 0000664 0000000 0000000 00000000000 15015405444 0021113 0 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/docs/source/changelog.md 0000664 0000000 0000000 00000000045 15015405444 0020316 0 ustar 00root root 0000000 0000000 ```{include} ../../CHANGELOG.md
```
bthome-ble-3.13.0/docs/source/conf.py 0000664 0000000 0000000 00000003632 15015405444 0017351 0 ustar 00root root 0000000 0000000 # Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
from typing import Any
# -- Project information -----------------------------------------------------
project = "BTHome BLE"
copyright = "2022, E. Klamer"
author = "E. Klamer"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"myst_parser",
]
# The suffix of source filenames.
source_suffix = [".rst", ".md"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns: list[Any] = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
bthome-ble-3.13.0/docs/source/contributing.md 0000664 0000000 0000000 00000000050 15015405444 0021072 0 ustar 00root root 0000000 0000000 ```{include} ../../CONTRIBUTING.md
```
bthome-ble-3.13.0/docs/source/index.md 0000664 0000000 0000000 00000000352 15015405444 0017477 0 ustar 00root root 0000000 0000000 # Welcome to BTHome BLE documentation!
```{toctree}
:caption: Installation & Usage
:maxdepth: 2
installation
usage
```
```{toctree}
:caption: Project Info
:maxdepth: 2
changelog
contributing
```
```{include} ../../README.md
```
bthome-ble-3.13.0/docs/source/installation.md 0000664 0000000 0000000 00000000262 15015405444 0021071 0 ustar 00root root 0000000 0000000 # Installation
The package is published on [PyPI](https://pypi.org/project/bthome-ble/) and can be installed with `pip` (or any equivalent):
```bash
pip install bthome-ble
```
bthome-ble-3.13.0/docs/source/usage.md 0000664 0000000 0000000 00000000203 15015405444 0017467 0 ustar 00root root 0000000 0000000 # Usage BTHome for DIY sensors
Detailed information about the use of BTHome BLE can be found on our [website](https://bthome.io).
bthome-ble-3.13.0/poetry.lock 0000664 0000000 0000000 00000441763 15015405444 0016031 0 ustar 00root root 0000000 0000000 # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "aiooui"
version = "0.1.9"
description = "Async OUI lookups"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "aiooui-0.1.9-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:64d904b43f14dd1d8d9fcf1684d9e2f558bc5e0bd68dc10023c93355c9027907"},
{file = "aiooui-0.1.9-py3-none-any.whl", hash = "sha256:737a5e62d8726540218c2b70e5f966d9912121e4644f3d490daf8f3c18b182e5"},
{file = "aiooui-0.1.9.tar.gz", hash = "sha256:e8c8bc59ab352419e0747628b4cce7c4e04d492574c1971e223401126389c5d8"},
]
[[package]]
name = "alabaster"
version = "0.7.16"
description = "A light, configurable Sphinx theme"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"},
{file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"},
]
[[package]]
name = "async-interrupt"
version = "1.2.2"
description = "Context manager to raise an exception when a future is done"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "async_interrupt-1.2.2-py3-none-any.whl", hash = "sha256:0a8deb884acfb5fe55188a693ae8a4381bbbd2cb6e670dac83869489513eec2c"},
{file = "async_interrupt-1.2.2.tar.gz", hash = "sha256:be4331a029b8625777905376a6dc1370984c8c810f30b79703f3ee039d262bf7"},
]
[[package]]
name = "babel"
version = "2.17.0"
description = "Internationalization utilities"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
{file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
]
[package.extras]
dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
[[package]]
name = "bleak"
version = "0.22.3"
description = "Bluetooth Low Energy platform Agnostic Klient"
optional = false
python-versions = "<3.14,>=3.8"
groups = ["main"]
files = [
{file = "bleak-0.22.3-py3-none-any.whl", hash = "sha256:1e62a9f5e0c184826e6c906e341d8aca53793e4596eeaf4e0b191e7aca5c461c"},
{file = "bleak-0.22.3.tar.gz", hash = "sha256:3149c3c19657e457727aa53d9d6aeb89658495822cd240afd8aeca4dd09c045c"},
]
[package.dependencies]
bleak-winrt = {version = ">=1.2.0,<2.0.0", markers = "platform_system == \"Windows\" and python_version < \"3.12\""}
dbus-fast = {version = ">=1.83.0,<3", markers = "platform_system == \"Linux\""}
pyobjc-core = {version = ">=10.3,<11.0", markers = "platform_system == \"Darwin\""}
pyobjc-framework-CoreBluetooth = {version = ">=10.3,<11.0", markers = "platform_system == \"Darwin\""}
pyobjc-framework-libdispatch = {version = ">=10.3,<11.0", markers = "platform_system == \"Darwin\""}
typing-extensions = {version = ">=4.7.0", markers = "python_version < \"3.12\""}
winrt-runtime = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
"winrt-Windows.Devices.Bluetooth" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
"winrt-Windows.Devices.Bluetooth.Advertisement" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
"winrt-Windows.Devices.Bluetooth.GenericAttributeProfile" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
"winrt-Windows.Devices.Enumeration" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
"winrt-Windows.Foundation" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
"winrt-Windows.Foundation.Collections" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
"winrt-Windows.Storage.Streams" = {version = ">=2,<3", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""}
[[package]]
name = "bleak-retry-connector"
version = "3.10.0"
description = "A connector for Bleak Clients that handles transient connection failures"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "bleak_retry_connector-3.10.0-py3-none-any.whl", hash = "sha256:caaf976320ef280f1145b557bf3b13697f71ef2c1070e1dc643709eb2d29fb1f"},
{file = "bleak_retry_connector-3.10.0.tar.gz", hash = "sha256:a95172bd56d2af677fb9e250291cde8c70d8f72381d423f64e48c828dffbc93b"},
]
[package.dependencies]
bleak = {version = ">=0.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.14\""}
bluetooth-adapters = {version = ">=0.15.2", markers = "python_version >= \"3.10\" and python_version < \"3.14\" and platform_system == \"Linux\""}
dbus-fast = {version = ">=1.14.0", markers = "platform_system == \"Linux\""}
[[package]]
name = "bleak-winrt"
version = "1.2.0"
description = "Python WinRT bindings for Bleak"
optional = false
python-versions = "*"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version < \"3.12\""
files = [
{file = "bleak-winrt-1.2.0.tar.gz", hash = "sha256:0577d070251b9354fc6c45ffac57e39341ebb08ead014b1bdbd43e211d2ce1d6"},
{file = "bleak_winrt-1.2.0-cp310-cp310-win32.whl", hash = "sha256:a2ae3054d6843ae0cfd3b94c83293a1dfd5804393977dd69bde91cb5099fc47c"},
{file = "bleak_winrt-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:677df51dc825c6657b3ae94f00bd09b8ab88422b40d6a7bdbf7972a63bc44e9a"},
{file = "bleak_winrt-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9449cdb942f22c9892bc1ada99e2ccce9bea8a8af1493e81fefb6de2cb3a7b80"},
{file = "bleak_winrt-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:98c1b5a6a6c431ac7f76aa4285b752fe14a1c626bd8a1dfa56f66173ff120bee"},
{file = "bleak_winrt-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:623ac511696e1f58d83cb9c431e32f613395f2199b3db7f125a3d872cab968a4"},
{file = "bleak_winrt-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:13ab06dec55469cf51a2c187be7b630a7a2922e1ea9ac1998135974a7239b1e3"},
{file = "bleak_winrt-1.2.0-cp38-cp38-win32.whl", hash = "sha256:5a36ff8cd53068c01a795a75d2c13054ddc5f99ce6de62c1a97cd343fc4d0727"},
{file = "bleak_winrt-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:810c00726653a962256b7acd8edf81ab9e4a3c66e936a342ce4aec7dbd3a7263"},
{file = "bleak_winrt-1.2.0-cp39-cp39-win32.whl", hash = "sha256:dd740047a08925bde54bec357391fcee595d7b8ca0c74c87170a5cbc3f97aa0a"},
{file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"},
]
[[package]]
name = "bluetooth-adapters"
version = "0.21.4"
description = "Tools to enumerate and find Bluetooth Adapters"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "bluetooth_adapters-0.21.4-py3-none-any.whl", hash = "sha256:ce2e8139cc9d7b103c21654c6309507979e469aae3efebcaeee9923080b0569b"},
{file = "bluetooth_adapters-0.21.4.tar.gz", hash = "sha256:a5a809ef7ba95ee673a78704f90ce34612deb3696269d1a6fd61f98642b99dd3"},
]
[package.dependencies]
aiooui = ">=0.1.1"
bleak = ">=0.21.1"
dbus-fast = {version = ">=1.21.0", markers = "platform_system == \"Linux\""}
uart-devices = ">=0.1.0"
usb-devices = ">=0.4.5"
[package.extras]
docs = ["Sphinx (>=5,<8)", "myst-parser (>=0.18,<3.1)", "sphinx-rtd-theme (>=1,<4)"]
[[package]]
name = "bluetooth-auto-recovery"
version = "1.5.1"
description = "Recover bluetooth adapters that are in an stuck state"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
files = [
{file = "bluetooth_auto_recovery-1.5.1-py3-none-any.whl", hash = "sha256:59751902004cad9a84b5a674b051113d0a653374c1cec271945f2862b2b15c8f"},
{file = "bluetooth_auto_recovery-1.5.1.tar.gz", hash = "sha256:16eaa20e3f86cb2818ce75d107f57534559cdd82ba015b2741667bcac929d506"},
]
[package.dependencies]
bluetooth-adapters = ">=0.16.0"
btsocket = ">=0.2.0"
PyRIC = ">=0.1.6.3"
usb-devices = ">=0.4.1"
[package.extras]
docs = ["Sphinx (>=5,<8)", "myst-parser (>=0.18,<3.1)", "sphinx-rtd-theme (>=1,<4)"]
[[package]]
name = "bluetooth-data-tools"
version = "1.28.1"
description = "Tools for converting bluetooth data and packets"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3a1d77ec99e4a3d8d7fc64f7a0891440ecd8b88d2bbdfbf5367e1d9beddcff41"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:539b6578885b4fb71186035e0c49e616cdd5ef84afa2d6a3f5fab18e7b8ea52d"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3389f6f3d4e2dfc1708b179a113a60290c8a33710a7b02534978a6cef42f9cca"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a77c7ba723ae1b97e0d9b260d77bb0f578e7356a87a9f74b319e9277f7df602"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965c1bf9c89e61a3055524c20c468c2953fb6a9ab5b40bb6369767855920fbbc"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:961e145e71c3266705ade6da069ecc34896cb2759a1b5638db15cf18ec2ef0b4"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5671ec965aff0b6e22de150b221413fb5d195429b73e623cd8935391652127f"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8ff4cefe9734145e3ddd7f21709c82e7b569ec674f252c35ac4a45a947399f40"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3d204b2508214ea895daabd5b6428e71949eefe4cc30fbe1be0feda960767bb9"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ef02152ab79226bd7909a1a48d4c4c8797f9e83b56a6b233fc5c4f56e8670154"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-win32.whl", hash = "sha256:5fc8d37470c841999695f68932a3956aea90cfbac2781a6bce8d81c083f76802"},
{file = "bluetooth_data_tools-1.28.1-cp310-cp310-win_amd64.whl", hash = "sha256:4fa7db50ca5ad66d0578b6af4a9e82502c6f1248db8d856b18d5490a6b90bc99"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e711e28b1d7bf07dea4a47d9dfd46355b30fb903607f5075339d9b98a074dbe"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:590b65a2a3b72c31ab37c103bc1fb0727880672783c33c857e6e95dd9233fe8e"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:349fb3c30d1839c74485efa142be565103b9b821e1d3c35cdd4a1f268e4e3809"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:825568201917b102f0204b22351bc854c77f530b26269c291e5f2d868e88349d"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4225ff50c81f5b282e2967f7de6338a34e725f28a5769372e1dd4c1fbaa5f622"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:88c72a520456004c415cc3b5802636c129c230c23a078a495531e1b3894fec06"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95735992c0df6f4259d76bea825f685484c4fa937184847377d1f2b69730b3a3"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e2d235d8b2c3ebf05df32df18db93e25dd8102d1c495a6010788bb7d15e60f16"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4218206a57db713d889f79aa6db11cd36df678532995ab4314a276142c9393af"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9061d8725a5681cf8ec8911282673f63a25dfb6c5d8134b84e916b0831e46f9c"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-win32.whl", hash = "sha256:4ca9c4ce92233ad2415fd33142914490c104abf2fbe2a220326f12810e5575a9"},
{file = "bluetooth_data_tools-1.28.1-cp311-cp311-win_amd64.whl", hash = "sha256:93f6466c2ad2945d6f2ccdad2a291efa46f5f0eab6c353830f5944d44b46c168"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae766a43303b897be1739d3c740d2bd59981c8f9c0681b316c3b9102787aa332"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13ebe3d99edf00ac0474f0252d364321be970f509c3c91d985459ba7de32b57b"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828c74ac6b41b2044eb26b282aacc2d04d8845409fe0c09b934cf5d436818516"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:593bf0e67575c6c3624df8761d15912429745704c901de27e643976a07e3d247"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:440cb050054c2cb33ccb2898873da9b35c529d77622c95dc381a64b540fd6997"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91fdd90d4cc2c25b1cf9e5d420b63daa7c32b56337851d7a6d91738fbc0308e9"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c884e73f32ee68bafbb0e05339f850649daa745a22fba5818b7845c91c0e162"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8bd5a430451977ca08f8dbd4ca7a1bb016ba70c1f4126831466999fd547be2f"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e9812fa5c50672898d4da1e901f736f6452437edc8788f674dc024595b0f4254"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:beaf78826e2b01aed00bdc66fc2d2a841899d57fbb7c1776cf1e7f4ee6ade9a0"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-win32.whl", hash = "sha256:e20634afd47cc80edcf5975892ea1d3a25f1d8f0fc9c45617c2e2f5db5e5ebe9"},
{file = "bluetooth_data_tools-1.28.1-cp312-cp312-win_amd64.whl", hash = "sha256:5b4e6329b26a3562e770bb1ce0f5b7a6e03d749f18d39ea8c3e7ff491d10f550"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c75802ff3576bd1f1e78934aed3f8bab3d3138d77b4ba47e024f4cb9c4e638f0"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e41d6643f2bfad77944515ad9a8d673539beca5bcccb3d9367255e452dd0c04e"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8249a8ffba384c2f3cfa464497d900b3040ca0fb94acaed7e7d43c963b8579"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1255c0c93846ab25ee9beab0670ca1a1aaee7912492cf74d2f77e54b61d81dda"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4598bf2d8eacca632c15b3c95e9a1bcc74bec392b823f1484df3e6f3df6d2024"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b08d346fa13846312082e5a7ada84ef4d4f9f24ff710da1e328edc04faa1d9c8"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:39f913ba76ce17a7664617941bab688597a7391c4065580de3fe354ddd71eb7b"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:493240081f1331e362c84b8a2c369e3f2c8cc35e3ffdf9eccd1366bed86565e6"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:76f72f64816352ecd3fd20c569e4ced1fd47fec5c2c5302b5560117d09085d14"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b28673e1b55664ed2bf475d9f985f3e860a03d697e59f43238b3457bef7776fb"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0d5b738088f0fc593e69817b3404dbf09b559842fab6027f7176eeca0438e831"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-win32.whl", hash = "sha256:1e96114fd3ad87d12631ba591c8b942ff27383ad4b310a2df230b2bbc2863532"},
{file = "bluetooth_data_tools-1.28.1-cp313-cp313-win_amd64.whl", hash = "sha256:0e3a7331fcd837025bb294af6790c7f61b56fd4aa0886434660dffab35ef422c"},
{file = "bluetooth_data_tools-1.28.1.tar.gz", hash = "sha256:47156468b220f4c7b3ed2e29b189fd782785b7a551ad5c61fecfe023dc4f6430"},
]
[package.dependencies]
cryptography = ">=41.0.3"
[package.extras]
docs = ["Sphinx (>=5,<9)", "myst-parser (>=0.18,<4.1)", "sphinx-rtd-theme (>=1,<4)"]
[[package]]
name = "bluetooth-sensor-state-data"
version = "1.9.0"
description = "Models for storing and converting Bluetooth Sensor State Data"
optional = false
python-versions = ">=3.11"
groups = ["main"]
files = [
{file = "bluetooth_sensor_state_data-1.9.0-py3-none-any.whl", hash = "sha256:adf3b17774011f188b8002c54b669e24cd60d3332ef42facd18baa60c56077e3"},
{file = "bluetooth_sensor_state_data-1.9.0.tar.gz", hash = "sha256:d289b86e4a65a5bccce6310f686d6f3e4ac4a2c56a13c2fccb475a6894667139"},
]
[package.dependencies]
bluetooth-data-tools = ">=1.28.0"
habluetooth = ">=3.42.0"
sensor-state-data = ">=2.0"
[package.extras]
docs = ["Sphinx (>=5,<8)", "myst-parser (>=0.18,<4.1)", "sphinx-rtd-theme (>=1,<4)"]
[[package]]
name = "btsocket"
version = "0.3.0"
description = "Python library for BlueZ Bluetooth Management API"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "btsocket-0.3.0-py2.py3-none-any.whl", hash = "sha256:949821c1b580a88e73804ad610f5173d6ae258e7b4e389da4f94d614344f1a9c"},
{file = "btsocket-0.3.0.tar.gz", hash = "sha256:7ea495de0ff883f0d9f8eea59c72ca7fed492994df668fe476b84d814a147a0d"},
]
[package.extras]
dev = ["bumpversion", "coverage", "pycodestyle", "pygments", "sphinx", "sphinx-rtd-theme", "twine"]
docs = ["pygments", "sphinx", "sphinx-rtd-theme"]
rel = ["bumpversion", "twine"]
test = ["coverage", "pycodestyle"]
[[package]]
name = "certifi"
version = "2025.1.31"
description = "Python package for providing Mozilla's CA Bundle."
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
]
[[package]]
name = "cffi"
version = "1.17.1"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
{file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
{file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
{file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
{file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
{file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
{file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
{file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
{file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
{file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
{file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
{file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
{file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
{file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
{file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
{file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
{file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
{file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
{file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
{file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
{file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
{file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
{file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
]
[package.dependencies]
pycparser = "*"
[[package]]
name = "charset-normalizer"
version = "3.4.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
{file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
{file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
{file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
{file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
{file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {main = "extra == \"docs\" and sys_platform == \"win32\"", dev = "sys_platform == \"win32\""}
[[package]]
name = "coverage"
version = "7.8.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"},
{file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"},
{file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"},
{file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"},
{file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"},
{file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"},
{file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"},
{file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"},
{file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"},
{file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"},
{file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"},
{file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"},
{file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"},
{file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"},
{file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"},
{file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"},
{file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"},
{file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"},
{file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"},
{file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"},
{file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"},
{file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"},
{file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"},
{file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"},
{file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"},
{file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"},
{file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"},
{file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"},
{file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"},
{file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"},
{file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"},
{file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"},
{file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"},
{file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"},
{file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"},
{file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"},
{file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"},
{file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"},
{file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"},
{file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"},
{file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"},
{file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"},
{file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"},
{file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"},
{file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"},
{file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"},
{file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"},
{file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"},
{file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"},
{file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"},
{file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"},
{file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"},
{file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"},
{file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"},
{file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"},
{file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"},
{file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"},
{file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"},
{file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"},
{file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"},
{file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"},
{file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"},
{file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"},
]
[package.extras]
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
[[package]]
name = "cryptography"
version = "45.0.3"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
groups = ["main"]
files = [
{file = "cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:7573d9eebaeceeb55285205dbbb8753ac1e962af3d9640791d12b36864065e71"},
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b"},
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae1e637f527750811588e4582988932c222f8251f7b7ea93739acb624e1487f"},
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ca932e11218bcc9ef812aa497cdf669484870ecbcf2d99b765d6c27a86000942"},
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af3f92b1dc25621f5fad065288a44ac790c5798e986a34d393ab27d2b27fcff9"},
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f8f8f0b73b885ddd7f3d8c2b2234a7d3ba49002b0223f58cfde1bedd9563c56"},
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9cc80ce69032ffa528b5e16d217fa4d8d4bb7d6ba8659c1b4d74a1b0f4235fca"},
{file = "cryptography-45.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c824c9281cb628015bfc3c59335163d4ca0540d49de4582d6c2637312907e4b1"},
{file = "cryptography-45.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5833bb4355cb377ebd880457663a972cd044e7f49585aee39245c0d592904578"},
{file = "cryptography-45.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bb5bf55dcb69f7067d80354d0a348368da907345a2c448b0babc4215ccd3497"},
{file = "cryptography-45.0.3-cp311-abi3-win32.whl", hash = "sha256:3ad69eeb92a9de9421e1f6685e85a10fbcfb75c833b42cc9bc2ba9fb00da4710"},
{file = "cryptography-45.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:97787952246a77d77934d41b62fb1b6f3581d83f71b44796a4158d93b8f5c490"},
{file = "cryptography-45.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:c92519d242703b675ccefd0f0562eb45e74d438e001f8ab52d628e885751fb06"},
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5edcb90da1843df85292ef3a313513766a78fbbb83f584a5a58fb001a5a9d57"},
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38deed72285c7ed699864f964a3f4cf11ab3fb38e8d39cfcd96710cd2b5bb716"},
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5555365a50efe1f486eed6ac7062c33b97ccef409f5970a0b6f205a7cfab59c8"},
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e4253ed8f5948a3589b3caee7ad9a5bf218ffd16869c516535325fece163dcc"},
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cfd84777b4b6684955ce86156cfb5e08d75e80dc2585e10d69e47f014f0a5342"},
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a2b56de3417fd5f48773ad8e91abaa700b678dc7fe1e0c757e1ae340779acf7b"},
{file = "cryptography-45.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:57a6500d459e8035e813bd8b51b671977fb149a8c95ed814989da682314d0782"},
{file = "cryptography-45.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f22af3c78abfbc7cbcdf2c55d23c3e022e1a462ee2481011d518c7fb9c9f3d65"},
{file = "cryptography-45.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:232954730c362638544758a8160c4ee1b832dc011d2c41a306ad8f7cccc5bb0b"},
{file = "cryptography-45.0.3-cp37-abi3-win32.whl", hash = "sha256:cb6ab89421bc90e0422aca911c69044c2912fc3debb19bb3c1bfe28ee3dff6ab"},
{file = "cryptography-45.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:d54ae41e6bd70ea23707843021c778f151ca258081586f0cfa31d936ae43d1b2"},
{file = "cryptography-45.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed43d396f42028c1f47b5fec012e9e12631266e3825e95c00e3cf94d472dac49"},
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fed5aaca1750e46db870874c9c273cd5182a9e9deb16f06f7bdffdb5c2bde4b9"},
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:00094838ecc7c6594171e8c8a9166124c1197b074cfca23645cee573910d76bc"},
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:92d5f428c1a0439b2040435a1d6bc1b26ebf0af88b093c3628913dd464d13fa1"},
{file = "cryptography-45.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:ec64ee375b5aaa354b2b273c921144a660a511f9df8785e6d1c942967106438e"},
{file = "cryptography-45.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:71320fbefd05454ef2d457c481ba9a5b0e540f3753354fff6f780927c25d19b0"},
{file = "cryptography-45.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:edd6d51869beb7f0d472e902ef231a9b7689508e83880ea16ca3311a00bf5ce7"},
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:555e5e2d3a53b4fabeca32835878b2818b3f23966a4efb0d566689777c5a12c8"},
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:25286aacb947286620a31f78f2ed1a32cded7be5d8b729ba3fb2c988457639e4"},
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:050ce5209d5072472971e6efbfc8ec5a8f9a841de5a4db0ebd9c2e392cb81972"},
{file = "cryptography-45.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dc10ec1e9f21f33420cc05214989544727e776286c1c16697178978327b95c9c"},
{file = "cryptography-45.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9eda14f049d7f09c2e8fb411dda17dd6b16a3c76a1de5e249188a32aeb92de19"},
{file = "cryptography-45.0.3.tar.gz", hash = "sha256:ec21313dd335c51d7877baf2972569f40a4291b76a0ce51391523ae358d05899"},
]
[package.dependencies]
cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""}
[package.extras]
docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""]
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""]
pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
sdist = ["build (>=1.0.0)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi (>=2024)", "cryptography-vectors (==45.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
test-randomorder = ["pytest-randomly"]
[[package]]
name = "dbus-fast"
version = "2.44.0"
description = "A faster version of dbus-next"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "platform_system == \"Linux\""
files = [
{file = "dbus_fast-2.44.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7e3eff5c37c782bcc5b73556ae0726cb1391bd5673b877c301761e8bfc93b21a"},
{file = "dbus_fast-2.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a052ee5c01d2bdc7083df941ffbb424bd76350e4755aa996d345aa5b21bbe54"},
{file = "dbus_fast-2.44.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:d8cf14a46cb8ebe51ff1d8c0be293c5656a60cb4321162277dd32ee683fc6cce"},
{file = "dbus_fast-2.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d891a46610f54e43a847fe2f670e9f6f68277bc495b1565801002f6ab6f0854"},
{file = "dbus_fast-2.44.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4977cad841b89f2654363f56ac9e13431bcae75e201ff4a42d8840f665da3ab3"},
{file = "dbus_fast-2.44.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2dcdd81d6c4a5cb165518cd50cc0827ad6c145b712ce9c4da618b427f87abe3f"},
{file = "dbus_fast-2.44.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7382fc12b6f84b47f47ccf2242f16f10ac2dc6a8966eea9944324b839fd7f2cc"},
{file = "dbus_fast-2.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9940b5d1cf4ada5278b601cd6335d66f66017be6dfb5104d704d6e80728265fe"},
{file = "dbus_fast-2.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639b0ceb3a6d8d5e72119e92ffc9e9b9308dc81c9ad64fc7db76fa5e5f08bedc"},
{file = "dbus_fast-2.44.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:e6221b137df24e79ddb422dcf2c77623f0ea0a75f3b940dc2c2b84bae73f869d"},
{file = "dbus_fast-2.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4de0dbc141bc1c10f0721f0600d3b7bdee8b050a7dbb9b1adbcca7aa8ea18dab"},
{file = "dbus_fast-2.44.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1397d6e8026e0ffad4e622c90e200df92d0e3b1b9c6f4646bf29ea58c3bb4f8b"},
{file = "dbus_fast-2.44.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:abde213a27491f1d7262148d05efcc137a70a4b0cf038b7ca6b0cda22905ce1c"},
{file = "dbus_fast-2.44.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f854a86f37a06e6d6f6f12c2401159919ea92e66736c91a96c4a5e3bfc7dcde4"},
{file = "dbus_fast-2.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dfc604949d822f5746ea5e699dea33c31b8407131fd98d7c5aa04b1ecc541a51"},
{file = "dbus_fast-2.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a68915536a4a37be5460a4c4837f0c2f909983d34fc7f8af84abdfa03cdf8f48"},
{file = "dbus_fast-2.44.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:4402e7f3d86bc751399ca09e8c143aa2bfce2ec27b76305e311a28f29b307c96"},
{file = "dbus_fast-2.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67061253a5f57526492adac595f6c7f8196a5bee13aa247cba335206358b3ffe"},
{file = "dbus_fast-2.44.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2cfcd0701f39a9789f8b61e2fc7d14c28c01b7045b0fc4dff88ae564aa912746"},
{file = "dbus_fast-2.44.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e3bc861080e8359bee1c7b7dc2e00106a7df5e9906a937fab960e97ddb864091"},
{file = "dbus_fast-2.44.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:734799964722c309f52a1aaf5f0bfab19ba5cc44c9704d0a3a5bc0084ea7c81e"},
{file = "dbus_fast-2.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ec3ef966b5bd48678130462e9814abb4eedba2394bdc1821022ff69a50653999"},
{file = "dbus_fast-2.44.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a728f59c9752f3f77668878df7068283726fa003c63661d8c809ca0a6e5e0a"},
{file = "dbus_fast-2.44.0-cp313-cp313-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:7b2b559eaf350cdadf6e33018121d2b0e80cc66047942505f19c4d1453cf8b26"},
{file = "dbus_fast-2.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721d7a5698c489fc2c0456fa80fd76d664e2808bd9683e3061749aee63925040"},
{file = "dbus_fast-2.44.0-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:045aa9c18e2449b7c3e84fa4caccdd1f9020d8a3d22f0dfcc11083ed4ccf2884"},
{file = "dbus_fast-2.44.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2fa27ef923f96418a1dcc331df3be972716cc7a70799e1d2be6ba52534300006"},
{file = "dbus_fast-2.44.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:405a9219e9b9d5b204f61a484c291855cc4d17cae14dcca2224bb3483b34dabb"},
{file = "dbus_fast-2.44.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420bb5a3ba2be81a3710159c85577fde176c117ffc4c3b6c87ca10babb1c4"},
{file = "dbus_fast-2.44.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:36d32327e3e3716b03a80044d661515a39d313a9fdd125ed278e9a213de2d24d"},
{file = "dbus_fast-2.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c33d71b9a8e68bd9b483efcb4bdeb0466b6347d2bbaeaf6c24e6d4b432e3c5f"},
{file = "dbus_fast-2.44.0-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:4684c18f4c6563359b01bc0abc906c37a11019f87451a94584e32ad706f0acd8"},
{file = "dbus_fast-2.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48736fdc97be846aaecc2e23f8a2dcdaced22e6a9eff1c71184e007d671e0348"},
{file = "dbus_fast-2.44.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6aae77badf1a3a69a54e2b8fe81bd027b6ce002c174d67a3354b3361a4b17096"},
{file = "dbus_fast-2.44.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:98d4fffc492964a204fa5117e1f89363c4d029c00b3232df24583cbd47adaeaf"},
{file = "dbus_fast-2.44.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7dec20398dfe0506b0e68810b87854695e00dc6552cefe32d8459f23c76d4e5"},
{file = "dbus_fast-2.44.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:955c5a8ad3152331310f8dabf1809f940f46adc50ab0102a9865b087113e0e37"},
{file = "dbus_fast-2.44.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59ca2a83ce8394c2f0a4105fa462d18dc937869dd73051a8ad5a1371314f0f94"},
{file = "dbus_fast-2.44.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:3a97d5f509cc6667e1884d5a6c2d7a3b5d62befde41fbf21741a043a526fd013"},
{file = "dbus_fast-2.44.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f4e6bda1302d1404b83043a6bdef0ee1800fc8926dbdc4fd5e6dd6085874af1"},
{file = "dbus_fast-2.44.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b431ca0904624fc98a74410547036108aa71ffdffd2d2704bbd8883a3d9541c"},
{file = "dbus_fast-2.44.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbe729bcf9b4942cfe99d53377a5c0043530ed98a3affe5245d76b19eb5c435d"},
{file = "dbus_fast-2.44.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:5e28b72452ad6f3cfbd8104d0ac4ab2d36a2a6aeea8c88a6bf1ec864c5e15a93"},
{file = "dbus_fast-2.44.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:475a1c5228e85695609724f57457d35d88e57f3b70c1f9bcfee26ec8821da110"},
{file = "dbus_fast-2.44.0.tar.gz", hash = "sha256:ccdf9a77447ce6cefaa869431fca2512e75ab4a4549c4b35a093988949e35f43"},
]
[[package]]
name = "docutils"
version = "0.21.2"
description = "Docutils -- Python Documentation Utilities"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"},
{file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"},
]
[[package]]
name = "habluetooth"
version = "3.48.2"
description = "High availability Bluetooth"
optional = false
python-versions = ">=3.11"
groups = ["main"]
files = [
{file = "habluetooth-3.48.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63f204d5e6121041c20d781778addc4e840ed394c6db0238b0a0447f82321e12"},
{file = "habluetooth-3.48.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87c0cb31a461861f4cc74fdf9d5d1dfd5acf780ee625b0b93f2ac73a44bb93"},
{file = "habluetooth-3.48.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53da45bfe263c6e54d4b6c67d07348a7f950e4335b12e042eb89eb9b2c33d17b"},
{file = "habluetooth-3.48.2-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:8cf7deee4f4a5180251621f1eec4f976a399e53cba68473f507286e1a5acefe7"},
{file = "habluetooth-3.48.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d264a0507a1c8e65fd82650f2da0c5e68811152aabba630aeadab02e754e85db"},
{file = "habluetooth-3.48.2-cp311-cp311-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6289ec7c0a16a314de8541f3ecf4eb9dac1311607be2aa1d621b964bf390d6ab"},
{file = "habluetooth-3.48.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:807165da371dfa0ef1c242b0f99d26a4458a1be58553c156c4225f741b72b895"},
{file = "habluetooth-3.48.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d07608d5e5f8bff942f91e021ba398353222b112c1e2c9a77f11de432226fc35"},
{file = "habluetooth-3.48.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:205e0de945655322f924caf994b50ef79a0fd182c3143b33a54c82b2a5b48ca0"},
{file = "habluetooth-3.48.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6c70f59f4435c91a86200a782405225129068fa17781654c82e66a4f1a4041c6"},
{file = "habluetooth-3.48.2-cp311-cp311-win32.whl", hash = "sha256:8407dcdf62272794475a21fe33fc784e8e59e85162b5b2a94ddc6780ad88fdee"},
{file = "habluetooth-3.48.2-cp311-cp311-win_amd64.whl", hash = "sha256:7f7585a8dc84e146cd2e9fb055d5a5c7f76706f9db128c1d45d8db96a916fd5f"},
{file = "habluetooth-3.48.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0eb36158205fccf6f47adff0d88f027a64e94934dd902e05b9e601f263344fa7"},
{file = "habluetooth-3.48.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ec3d0e248e017c175112e8d9d235d0aa549d714de372446f2ac13ec92eaf1c3"},
{file = "habluetooth-3.48.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8984a2ed1565b86d50524a3c22b0d552228e330b8250ee9b290356083600e593"},
{file = "habluetooth-3.48.2-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:897e5ea90e6acf4508b242ec869b404322e46914553cace07e8fb220f8c62697"},
{file = "habluetooth-3.48.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:644c5af498cf6a5499dfbec46034363ea1a515718c3260293bab8673713c9e6c"},
{file = "habluetooth-3.48.2-cp312-cp312-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d4e37319562287a5d37857815d39fa6920894a87a4b7e781ac17691682e30a7"},
{file = "habluetooth-3.48.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e627e0cf211884d182aa36f074e91105dd174333f43e26a9fa04311869997d69"},
{file = "habluetooth-3.48.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fd7f8612b076f184aac687f2700e771983402373e209072922d2740d7c3241c8"},
{file = "habluetooth-3.48.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8e0862d6421de6a24756e78c50df763064beb273effd6d8483b700bf3f11a0d"},
{file = "habluetooth-3.48.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:adfa1edfc4c387d3ed6fcc899529624163b560e55fd62cd34fe19c5d108925c5"},
{file = "habluetooth-3.48.2-cp312-cp312-win32.whl", hash = "sha256:54e8c6d5ebb83504b810dce662fd57d18cc72179af3ca5250cdcb9311b72d55c"},
{file = "habluetooth-3.48.2-cp312-cp312-win_amd64.whl", hash = "sha256:13c5807cd30c1d04b86e1b8065e034a0fc72608f5b93915f6359c704f5aae9d7"},
{file = "habluetooth-3.48.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f53c0768428451b0a427d1aaeeb8adb959e11aa682f44d7ae4de78ed583786f5"},
{file = "habluetooth-3.48.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc3b202cb93dbaebbb8277da2b9d91c27308dd21e8237ea88a196c698f96ee98"},
{file = "habluetooth-3.48.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d0c81eeebbbc07d31b4f37e8c116492a8e6d4d09a35ac4a357ad0a0e07ebfea"},
{file = "habluetooth-3.48.2-cp313-cp313-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:33edcb88175957a251f0592a223f9c4c9abf6ed6f880c8e96ac6a186e9b6f864"},
{file = "habluetooth-3.48.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74d129b92b5e55fd1dc4ef6adf8c135dc37e64625c0cc5e9fc8f902aa050bb81"},
{file = "habluetooth-3.48.2-cp313-cp313-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82fcb18d7e1d6708ba92069a67f4572a1d506b4783a637cd7a2058e2cf56cbdc"},
{file = "habluetooth-3.48.2-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:6b3cc80a241f5dbaf8c2e15fdfd96fd17dbb406cb16f01cfcbbfa0efd54724ae"},
{file = "habluetooth-3.48.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba2cd970d992ba674886a39ebe2e3f03cbddd3b2af2c6f2f04a9b996a4ca012e"},
{file = "habluetooth-3.48.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ada96d5e1dd26e7c211df8c851d285d4a9f6ca46c731b2a96d56eeed791c0c40"},
{file = "habluetooth-3.48.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62b6d37ce4d13b7f43d24e9aa7f51642c096c88fa408e76dc89dea81acb01caa"},
{file = "habluetooth-3.48.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d59cc6ce6f78ab685bbead78561bdb94567399434020d276e461507d875455a3"},
{file = "habluetooth-3.48.2-cp313-cp313-win32.whl", hash = "sha256:dd7d96fef4b4cb5642c90b340214f5428445e620f6699853a8b2878f53dc18b3"},
{file = "habluetooth-3.48.2-cp313-cp313-win_amd64.whl", hash = "sha256:6f58437ec9c1409e5b0d04c7d6b5cf9ba2386787a16c81228d408d3a689f0e5b"},
{file = "habluetooth-3.48.2.tar.gz", hash = "sha256:50ae656e7c532b7e93e1360fd257f5985ddc692d54c1e598b6c6fdbc8062d75d"},
]
[package.dependencies]
async-interrupt = ">=1.1.1"
bleak = ">=0.21.1"
bleak-retry-connector = ">=3.9.0"
bluetooth-adapters = ">=0.16.1"
bluetooth-auto-recovery = ">=1.5.1"
bluetooth-data-tools = ">=1.28.0"
dbus-fast = {version = ">=2.30.2", markers = "platform_system == \"Linux\""}
[[package]]
name = "idna"
version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "imagesize"
version = "1.4.1"
description = "Getting image size from png/jpeg/jpeg2000/gif file"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
{file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
]
[[package]]
name = "iniconfig"
version = "2.1.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
]
[[package]]
name = "jinja2"
version = "3.1.6"
description = "A very fast and expressive template engine."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
]
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
code-style = ["pre-commit (>=3.0,<4.0)"]
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "markupsafe"
version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
]
[[package]]
name = "mdit-py-plugins"
version = "0.4.2"
description = "Collection of plugins for markdown-it-py"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"},
{file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"},
]
[package.dependencies]
markdown-it-py = ">=1.0.0,<4.0.0"
[package.extras]
code-style = ["pre-commit"]
rtd = ["myst-parser", "sphinx-book-theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "myst-parser"
version = "4.0.1"
description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser,"
optional = true
python-versions = ">=3.10"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"},
{file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"},
]
[package.dependencies]
docutils = ">=0.19,<0.22"
jinja2 = "*"
markdown-it-py = ">=3.0,<4.0"
mdit-py-plugins = ">=0.4.1,<1.0"
pyyaml = "*"
sphinx = ">=7,<9"
[package.extras]
code-style = ["pre-commit (>=4.0,<5.0)"]
linkify = ["linkify-it-py (>=2.0,<3.0)"]
rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"]
testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"]
testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"]
[[package]]
name = "packaging"
version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
markers = {main = "extra == \"docs\""}
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pycparser"
version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[[package]]
name = "pygments"
version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyobjc-core"
version = "10.3.2"
description = "Python<->ObjC Interoperability Module"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_core-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb40672d682851a5c7fd84e5041c4d069b62076168d72591abb5fcc871bb039"},
{file = "pyobjc_core-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cea5e77659619ad93c782ca07644b6efe7d7ec6f59e46128843a0a87c1af511a"},
{file = "pyobjc_core-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:16644a92fb9661de841ba6115e5354db06a1d193a5e239046e840013c7b3874d"},
{file = "pyobjc_core-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:76b8b911d94501dac89821df349b1860bb770dce102a1a293f524b5b09dd9462"},
{file = "pyobjc_core-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8c6288fdb210b64115760a4504efbc4daffdc390d309e9318eb0e3e3b78d2828"},
{file = "pyobjc_core-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:87901e9f7032f33eb4fa884e407bf2744d5a0791b379bfca783982a02be3f7fb"},
{file = "pyobjc_core-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:636971ab48a4198ca129e149fe58ccf85a7b4a9b93d27f5ae920d88eb2655431"},
{file = "pyobjc_core-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:48e9ac3af42b2340dae709a8b894f5ef7e5132d8546adcd1797cffcc449dabdc"},
{file = "pyobjc_core-10.3.2.tar.gz", hash = "sha256:dbf1475d864ce594288ce03e94e3a98dc7f0e4639971eb1e312bdf6661c21e0e"},
]
[[package]]
name = "pyobjc-framework-cocoa"
version = "10.3.2"
description = "Wrappers for the Cocoa frameworks on macOS"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_framework_Cocoa-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61f44c2adab28fdf3aa3d593c9497a2d9ceb9583ed9814adb48828c385d83ff4"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7caaf8b260e81b27b7b787332846f644b9423bfc1536f6ec24edbde59ab77a87"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c49e99fc4b9e613fb308651b99d52a8a9ae9916c8ef27aa2f5d585b6678a59bf"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1161b5713f9b9934c12649d73a6749617172e240f9431eff9e22175262fdfda"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:08e48b9ee4eb393447b2b781d16663b954bd10a26927df74f92e924c05568d89"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7faa448d2038ae0e0287a326d390002e744bb6470e45995e2dbd16c892e4495a"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:fcd53fee2be9708576617994b107aedc2c40824b648cd51e780e8399c0a447b6"},
{file = "pyobjc_framework_Cocoa-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:838fcf0d10674bde9ff64a3f20c0e188f2dc5e804476d80509b81c4ac1dabc59"},
{file = "pyobjc_framework_cocoa-10.3.2.tar.gz", hash = "sha256:673968e5435845bef969bfe374f31a1a6dc660c98608d2b84d5cae6eafa5c39d"},
]
[package.dependencies]
pyobjc-core = ">=10.3.2"
[[package]]
name = "pyobjc-framework-corebluetooth"
version = "10.3.2"
description = "Wrappers for the framework CoreBluetooth on macOS"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:af3e2f935a6a7e5b009b4cf63c64899592a7b46c3ddcbc8f2e28848842ef65f4"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_13_universal2.whl", hash = "sha256:973b78f47c7e2209a475e60bcc7d1b4a87be6645d39b4e8290ee82640e1cc364"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4bafdf1be15eae48a4878dbbf1bf19877ce28cbbba5baa0267a9564719ee736e"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4d7dc7494de66c850bda7b173579df7481dc97046fa229d480fe9bf90b2b9651"},
{file = "pyobjc_framework_CoreBluetooth-10.3.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:62e09e730f4d98384f1b6d44718812195602b3c82d5c78e09f60e8a934e7b266"},
{file = "pyobjc_framework_corebluetooth-10.3.2.tar.gz", hash = "sha256:c0a077bc3a2466271efa382c1e024630bc43cc6f9ab8f3f97431ad08b1ad52bb"},
]
[package.dependencies]
pyobjc-core = ">=10.3.2"
pyobjc-framework-Cocoa = ">=10.3.2"
[[package]]
name = "pyobjc-framework-libdispatch"
version = "10.3.2"
description = "Wrappers for libdispatch on macOS"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_system == \"Darwin\""
files = [
{file = "pyobjc_framework_libdispatch-10.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35233a8b1135567c7696087f924e398799467c7f129200b559e8e4fa777af860"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:061f6aa0f88d11d993e6546ec734303cb8979f40ae0f5cd23541236a6b426abd"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6bb528f34538f35e1b79d839dbfc398dd426990e190d9301fe2d811fddc3da62"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1357729d5fded08fbf746834ebeef27bee07d6acb991f3b8366e8f4319d882c4"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:210398f9e1815ceeff49b578bf51c2d6a4a30d4c33f573da322f3d7da1add121"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e7ae5988ac0b369ad40ce5497af71864fac45c289fa52671009b427f03d6871f"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:f9d51d52dff453a4b19c096171a6cd31dd5e665371c00c1d72d480e1c22cd3d4"},
{file = "pyobjc_framework_libdispatch-10.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ef755bcabff2ea8db45603a8294818e0eeae85bf0b7b9d59e42f5947a26e33b9"},
{file = "pyobjc_framework_libdispatch-10.3.2.tar.gz", hash = "sha256:e9f4311fbf8df602852557a98d2a64f37a9d363acf4d75634120251bbc7b7304"},
]
[package.dependencies]
pyobjc-core = ">=10.3.2"
pyobjc-framework-Cocoa = ">=10.3.2"
[[package]]
name = "pyric"
version = "0.1.6.3"
description = "Python Wireless Library"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "PyRIC-0.1.6.3.tar.gz", hash = "sha256:b539b01cafebd2406c00097f94525ea0f8ecd1dd92f7731f43eac0ef16c2ccc9"},
]
[[package]]
name = "pytest"
version = "8.3.5"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.5,<2"
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "6.1.1"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"},
{file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"},
]
[package.dependencies]
coverage = {version = ">=7.5", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]]
name = "requests"
version = "2.32.3"
description = "Python HTTP for Humans."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "roman-numerals-py"
version = "3.1.0"
description = "Manipulate well-formed Roman numerals"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"},
{file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"},
]
[package.extras]
lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"]
test = ["pytest (>=8)"]
[[package]]
name = "sensor-state-data"
version = "2.18.1"
description = "Models for storing and converting Sensor Data state"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "sensor_state_data-2.18.1-py3-none-any.whl", hash = "sha256:45a223acf5d4aefde45c028fa810c7925db6448984097aa1b500fe4f206d113f"},
{file = "sensor_state_data-2.18.1.tar.gz", hash = "sha256:25f17ed98748ae006ddab82d5013cf30301daaf23526d1992f99c4dc0beb49c3"},
]
[package.extras]
docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"]
[[package]]
name = "snowballstemmer"
version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
[[package]]
name = "sphinx"
version = "8.2.3"
description = "Python documentation generator"
optional = true
python-versions = ">=3.11"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"},
{file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"},
]
[package.dependencies]
alabaster = ">=0.7.14"
babel = ">=2.13"
colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
docutils = ">=0.20,<0.22"
imagesize = ">=1.3"
Jinja2 = ">=3.1"
packaging = ">=23.0"
Pygments = ">=2.17"
requests = ">=2.30.0"
roman-numerals-py = ">=1.0.0"
snowballstemmer = ">=2.2"
sphinxcontrib-applehelp = ">=1.0.7"
sphinxcontrib-devhelp = ">=1.0.6"
sphinxcontrib-htmlhelp = ">=2.0.6"
sphinxcontrib-jsmath = ">=1.0.1"
sphinxcontrib-qthelp = ">=1.0.6"
sphinxcontrib-serializinghtml = ">=1.1.9"
[package.extras]
docs = ["sphinxcontrib-websupport"]
lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"]
test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
[[package]]
name = "sphinx-rtd-theme"
version = "3.0.2"
description = "Read the Docs theme for Sphinx"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"},
{file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"},
]
[package.dependencies]
docutils = ">0.18,<0.22"
sphinx = ">=6,<9"
sphinxcontrib-jquery = ">=4,<5"
[package.extras]
dev = ["bump2version", "transifex-client", "twine", "wheel"]
[[package]]
name = "sphinxcontrib-applehelp"
version = "2.0.0"
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
{file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-devhelp"
version = "2.0.0"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
{file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-htmlhelp"
version = "2.1.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
{file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["html5lib", "pytest"]
[[package]]
name = "sphinxcontrib-jquery"
version = "4.1"
description = "Extension to include jQuery on newer Sphinx releases"
optional = true
python-versions = ">=2.7"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"},
{file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"},
]
[package.dependencies]
Sphinx = ">=1.8"
[[package]]
name = "sphinxcontrib-jsmath"
version = "1.0.1"
description = "A sphinx extension which renders display math in HTML via JavaScript"
optional = true
python-versions = ">=3.5"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
]
[package.extras]
test = ["flake8", "mypy", "pytest"]
[[package]]
name = "sphinxcontrib-qthelp"
version = "2.0.0"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
{file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["defusedxml (>=0.7.1)", "pytest"]
[[package]]
name = "sphinxcontrib-serializinghtml"
version = "2.0.0"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
{file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "typing-extensions"
version = "4.13.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version < \"3.12\""
files = [
{file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"},
{file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"},
]
[[package]]
name = "uart-devices"
version = "0.1.1"
description = "UART Devices for Linux"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "uart_devices-0.1.1-py3-none-any.whl", hash = "sha256:55bc8cce66465e90b298f0910e5c496bc7be021341c5455954cf61c6253dc123"},
{file = "uart_devices-0.1.1.tar.gz", hash = "sha256:3a52c4ae0f5f7400ebe1ae5f6e2a2d40cc0b7f18a50e895236535c4e53c6ed34"},
]
[[package]]
name = "urllib3"
version = "2.3.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"docs\""
files = [
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
]
[package.extras]
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "usb-devices"
version = "0.4.5"
description = "Tools for mapping, describing, and resetting USB devices"
optional = false
python-versions = ">=3.9,<4.0"
groups = ["main"]
files = [
{file = "usb_devices-0.4.5-py3-none-any.whl", hash = "sha256:8a415219ef1395e25aa0bddcad484c88edf9673acdeae8a07223ca7222a01dcf"},
{file = "usb_devices-0.4.5.tar.gz", hash = "sha256:9b5c7606df2bc791c6c45b7f76244a0cbed83cb6fa4c68791a143c03345e195d"},
]
[[package]]
name = "winrt-runtime"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_runtime-2.3.0-cp310-cp310-win32.whl", hash = "sha256:5c22ed339b420a6026134e28281b25078a9e6755eceb494dce5d42ee5814e3fd"},
{file = "winrt_runtime-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f3ef0d6b281a8d4155ea14a0f917faf82a004d4996d07beb2b3d2af191503fb1"},
{file = "winrt_runtime-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:93ce23df52396ed89dfe659ee0e1a968928e526b9c577942d4a54ad55b333644"},
{file = "winrt_runtime-2.3.0-cp311-cp311-win32.whl", hash = "sha256:352d70864846fd7ec89703845b82a35cef73f42d178a02a4635a38df5a61c0f8"},
{file = "winrt_runtime-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:286e6036af4903dd830398103c3edd110a46432347e8a52ba416d937c0e1f5f9"},
{file = "winrt_runtime-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:44d0f0f48f2f10c02b885989e8bbac41d7bf9c03550b20ddf562100356fca7a9"},
{file = "winrt_runtime-2.3.0-cp312-cp312-win32.whl", hash = "sha256:03d3e4aedc65832e57c0dbf210ec2a9d7fb2819c74d420ba889b323e9fa5cf28"},
{file = "winrt_runtime-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0dc636aec2f4ee6c3849fa59dae10c128f4a908f0ce452e91af65d812ea66dcb"},
{file = "winrt_runtime-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:d9f140c71e4f3bf7bf7d6853b246eab2e1632c72f218ff163aa41a74b576736f"},
{file = "winrt_runtime-2.3.0-cp313-cp313-win32.whl", hash = "sha256:77f06df6b7a6cb536913ae455e30c1733d31d88dafe2c3cd8c3d0e2bcf7e2a20"},
{file = "winrt_runtime-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:7388774b74ea2f4510ab3a98c95af296665ebe69d9d7e2fd7ee2c3fc5856099e"},
{file = "winrt_runtime-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:0d3a4ac7661cad492d51653054e63328b940a6083c1ee1dd977f90069cb8afaa"},
{file = "winrt_runtime-2.3.0-cp39-cp39-win32.whl", hash = "sha256:cd7bce2c7703054e7f64d11be665e9728e15d9dae0d952a51228fe830e0c4b55"},
{file = "winrt_runtime-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:2da01af378ab9374a3a933da97543f471a676a3b844318316869bffeff811e8a"},
{file = "winrt_runtime-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:1c6bbfcc7cbe1c8159ed5d776b30b7f1cbc2c6990803292823b0788c22d75636"},
{file = "winrt_runtime-2.3.0.tar.gz", hash = "sha256:bb895a2b8c74b375781302215e2661914369c625aa1f8df84f8d37691b22db77"},
]
[[package]]
name = "winrt-windows-devices-bluetooth"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp310-cp310-win32.whl", hash = "sha256:554aa6d0ca4bebc22a45f19fa60db1183a2b5643468f3c95cf0ebc33fbc1b0d0"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:cec2682e10431f027c1823647772671fb09bebc1e8a00021a3651120b846d36f"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:b4d42faef99845de2aded4c75c906f03cc3ba3df51fb4435e4cc88a19168cf99"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp311-cp311-win32.whl", hash = "sha256:64e0992175d4d5a1160179a8c586c2202a0edbd47a5b6da4efdbc8bb601f2f99"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:0830111c077508b599062fbe2d817203e4efa3605bd209cf4a3e03388ec39dda"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:3943d538cb7b6bde3fd8741591eb6e23487ee9ee6284f05428b205e7d10b6d92"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp312-cp312-win32.whl", hash = "sha256:544ed169039e6d5e250323cc18c87967cfeb4d3d09ce354fd7c5fd2283f3bb98"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:f7becf095bf9bc999629fcb6401a88b879c3531b3c55c820e63259c955ddc06c"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:a6a2980409c855b4e5dab0be9bde9f30236292ac1fc994df959fa5a518cd6690"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp313-cp313-win32.whl", hash = "sha256:82f443be43379d4762e72633047c82843c873b6f26428a18855ca7b53e1958d7"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:8b407da87ab52315c2d562a75d824dcafcae6e1628031cdb971072a47eb78ff0"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:e36d0b487bc5b64662b8470085edf8bfa5a220d7afc4f2e8d7faa3e3ac2bae80"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp39-cp39-win32.whl", hash = "sha256:6553023433edf5a75767e8962bf492d0623036975c7d8373d5bbccc633a77bbc"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:77bdeadb043190c40ebbad462cd06e38b6461bc976bc67daf587e9395c387aae"},
{file = "winrt_Windows.Devices.Bluetooth-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:c588ab79b534fedecce48f7082b419315e8d797d0120556166492e603e90d932"},
{file = "winrt_windows_devices_bluetooth-2.3.0.tar.gz", hash = "sha256:a1204b71c369a0399ec15d9a7b7c67990dd74504e486b839bf81825bd381a837"},
]
[package.dependencies]
winrt-runtime = "2.3.0"
[package.extras]
all = ["winrt-Windows.Devices.Bluetooth.GenericAttributeProfile[all] (==2.3.0)", "winrt-Windows.Devices.Bluetooth.Rfcomm[all] (==2.3.0)", "winrt-Windows.Devices.Enumeration[all] (==2.3.0)", "winrt-Windows.Devices.Radios[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Networking[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)"]
[[package]]
name = "winrt-windows-devices-bluetooth-advertisement"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp310-cp310-win32.whl", hash = "sha256:4386498e7794ed383542ea868f0aa2dd8fb5f09f12bdffde024d12bd9f5a3756"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6fa25b2541d2898ae17982e86e0977a639b04f75119612cb46e1719474513fd"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:b200ff5acd181353f61f5b6446176faf78a61867d8c1d21e77a15e239d2cdf6b"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp311-cp311-win32.whl", hash = "sha256:e56ad277813b48e35a3074f286c55a7a25884676e23ef9c3fc12349a42cb8fa4"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d6533fef6a5914dc8d519b83b1841becf6fd2f37163d6e07df318a6a6118f194"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:8f4369cb0108f8ee0cace559f9870b00a4dde3fc1abd52f84adba08bc733825c"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp312-cp312-win32.whl", hash = "sha256:d729d989acd7c1d703e2088299b6e219089a415db4a7b80cd52fdc507ec3ce95"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d3d258d4388a2b46f2e46f2fbdede1bf327eaa9c2dd4605f8a7fe454077c49e"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:d8c12457b00a79f8f1058d7a51bd8e7f177fb66e31389469e75b1104f6358921"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp313-cp313-win32.whl", hash = "sha256:ac1e55a350881f82cb51e162cb7a4b5d9359e9e5fbde922de802404a951d64ec"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0fc339340fb8be21c1c829816a49dc31b986c6d602d113d4a49ee8ffaf0e2396"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:da63d9c56edcb3b2d5135e65cc8c9c4658344dd480a8a2daf45beb2106f17874"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp39-cp39-win32.whl", hash = "sha256:e98c6ae4b0afd3e4f3ab4fa06e84d6017ff9242146a64e3bad73f7f34183a076"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdc485f4143fbbb3ae0c9c9ad03b1021a5cb233c6df65bf56ac14f8e22c918c3"},
{file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:7af519cc895be84d6974e9f70d102545a5e8db05e065903b0fd84521218e60a9"},
{file = "winrt_windows_devices_bluetooth_advertisement-2.3.0.tar.gz", hash = "sha256:c8adbec690b765ca70337c35efec9910b0937a40a0a242184ea295367137f81c"},
]
[package.dependencies]
winrt-runtime = "2.3.0"
[package.extras]
all = ["winrt-Windows.Devices.Bluetooth[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)"]
[[package]]
name = "winrt-windows-devices-bluetooth-genericattributeprofile"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp310-cp310-win32.whl", hash = "sha256:1ec75b107370827874d8435a47852d0459cb66d5694e02a833e0a75c4748e847"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:0a178aa936abbc56ae1cc54a222dee4a34ce6c09506a5b592d4f7d04dbe76b95"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:b7067b8578e19ad17b28694090d5b000fee57db5b219462155961b685d71fba5"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp311-cp311-win32.whl", hash = "sha256:e0aeba201e20b6c4bc18a4336b5b07d653d4ab4c9c17a301613db680a346cd5e"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:f87b3995de18b98075ec2b02afc7252873fa75e7c840eb770d7bfafb4fda5c12"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:7dccce04ec076666001efca8e2484d0ec444b2302ae150ef184aa253b8cfba09"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp312-cp312-win32.whl", hash = "sha256:1b97ef2ab9c9f5bae984989a47565d0d19c84969d74982a2664a4a3485cb8274"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:5fac2c7b301fa70e105785d7504176c76e4d824fc3823afed4d1ab6a7682272c"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:353fdccf2398b2a12e0835834cff8143a7efd9ba877fb5820fdcce531732b500"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp313-cp313-win32.whl", hash = "sha256:f414f793767ccc56d055b1c74830efb51fa4cbdc9163847b1a38b1ee35778f49"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ef35d9cda5bbdcc55aa7eaf143ab873227d6ee467aaf28edbd2428f229e7c94"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:6a9e7308ba264175c2a9ee31f6cf1d647cb35ee9a1da7350793d8fe033a6b9b8"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp39-cp39-win32.whl", hash = "sha256:aea58f7e484cf3480ab9472a3e99b61c157b8a47baae8694bc7400ea5335f5dc"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:992b792a9e7f5771ccdc18eec4e526a11f23b75d9be5de3ec552ff719333897a"},
{file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:66b030a9cc6099dafe4253239e8e625cc063bb9bb115bebed6260d92dd86f6b1"},
{file = "winrt_windows_devices_bluetooth_genericattributeprofile-2.3.0.tar.gz", hash = "sha256:f40f94bf2f7243848dc10e39cfde76c9044727a05e7e5dfb8cb7f062f3fd3dda"},
]
[package.dependencies]
winrt-runtime = "2.3.0"
[package.extras]
all = ["winrt-Windows.Devices.Bluetooth[all] (==2.3.0)", "winrt-Windows.Devices.Enumeration[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)"]
[[package]]
name = "winrt-windows-devices-enumeration"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp310-cp310-win32.whl", hash = "sha256:461360ab47967f39721e71276fdcfe87ad2f71ba7b09d721f2f88bcdf16a6924"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a7d7b01d43d5dcc1f3846db12f4c552155efae75469f36052623faed7f0f74a8"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:6478fbe6f45172a9911c15b061ec9b0f30c9f4845ba3fd1e9e1bb78c1fb691c4"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp311-cp311-win32.whl", hash = "sha256:30be5cba8e9e81ea8dd514ba1300b5bb14ad7cc4e32efe908ddddd14c73e7f61"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86c2a1865e0a0146dd4f51f17e3d773d3e6732742f61838c05061f28738c6dbd"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:1b50d9304e49a9f04bc8139831b75be968ff19a1f50529d5eb0081dae2103d92"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp312-cp312-win32.whl", hash = "sha256:42ed0349f0290a1b0a101425a06196c5d5db1240db6f8bd7d2204f23c48d727b"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:83e385fbf85b9511699d33c659673611f42b98bd3a554a85b377a34cc3b68b2e"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:26f855caee61c12449c6b07e22ea1ad470f8daa24223d8581e1fe622c70b48a8"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp313-cp313-win32.whl", hash = "sha256:a5f2cff6ee584e5627a2246bdbcd1b3a3fd1e7ae0741f62c59f7d5a5650d5791"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:7516171521aa383ccdc8f422cc202979a2359d0d1256f22852bfb0b55d9154f0"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:80d01dfffe4b548439242f3f7a737189354768b203cca023dc29b267dfe5595a"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp39-cp39-win32.whl", hash = "sha256:990a375cd8edc2d30b939a49dcc1349ede3a4b8e4da78baf0de5e5711d3a4f00"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e7bedf0eac2066d7d37b1d34071b95bb57024e9e083867be1d24e916e012ac0"},
{file = "winrt_Windows.Devices.Enumeration-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:c53b673b80ba794f1c1320a5e0a14d795193c3f64b8132ebafba2f49c7301c2f"},
{file = "winrt_windows_devices_enumeration-2.3.0.tar.gz", hash = "sha256:a14078aac41432781acb0c950fcdcdeb096e2f80f7591a3d46435f30221fc3eb"},
]
[package.dependencies]
winrt-runtime = "2.3.0"
[package.extras]
all = ["winrt-Windows.ApplicationModel.Background[all] (==2.3.0)", "winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Security.Credentials[all] (==2.3.0)", "winrt-Windows.Storage.Streams[all] (==2.3.0)", "winrt-Windows.UI.Popups[all] (==2.3.0)", "winrt-Windows.UI[all] (==2.3.0)"]
[[package]]
name = "winrt-windows-foundation"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_Windows.Foundation-2.3.0-cp310-cp310-win32.whl", hash = "sha256:ea7b0e82be5c05690fedaf0dac5aa5e5fefd7ebf90b1497e5993197d305d916d"},
{file = "winrt_Windows.Foundation-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:6807dd40f8ecd6403679f6eae0db81674fdcf33768d08fdee66e0a17b7a02515"},
{file = "winrt_Windows.Foundation-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:0a861815e97ace82583210c03cf800507b0c3a97edd914bfffa5f88de1fbafcc"},
{file = "winrt_Windows.Foundation-2.3.0-cp311-cp311-win32.whl", hash = "sha256:c79b3d9384128b6b28c2483b4600f15c5d32c1f6646f9d77fdb3ee9bbaef6f81"},
{file = "winrt_Windows.Foundation-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fdd9c4914070dc598f5961d9c7571dd7d745f5cc60347603bf39d6ee921bd85c"},
{file = "winrt_Windows.Foundation-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:62bbb0ffa273551d33fd533d6e09b6f9f633dc214225d483722af47d2525fb84"},
{file = "winrt_Windows.Foundation-2.3.0-cp312-cp312-win32.whl", hash = "sha256:d36f472ac258e79eee6061e1bb4ce50bfd200f9271392d23479c800ca6aee8d1"},
{file = "winrt_Windows.Foundation-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8de9b5e95a3fdabdb45b1952e05355dd5a678f80bf09a54d9f966dccc805b383"},
{file = "winrt_Windows.Foundation-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:37da09c08c9c772baedb1958e5ee116fe63809f33c6820c69750f340b3dda292"},
{file = "winrt_Windows.Foundation-2.3.0-cp313-cp313-win32.whl", hash = "sha256:2b00fad3f2a3859ccae41eee12ab44434813a371c2f3003b4f2419e5eecb4832"},
{file = "winrt_Windows.Foundation-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:686619932b2a2c689cbebc7f5196437a45fd2056656ef130bb10240bb111086a"},
{file = "winrt_Windows.Foundation-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b38dcb83fe82a7da9a57d7d5ad5deb09503b5be6d9357a9fd3016ca31673805d"},
{file = "winrt_Windows.Foundation-2.3.0-cp39-cp39-win32.whl", hash = "sha256:2d6922de4dc38061b86d314c7319d7c6bd78a52d64ee0c93eb81474bddb499bc"},
{file = "winrt_Windows.Foundation-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:1513e43adff3779d2f611d8bdf9350ac1a7c04389e9e6b1d777c5cd54f46e4fc"},
{file = "winrt_Windows.Foundation-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:c811e4a4f79b947fbbb50f74d34ef6840dd2dd26e0199bd61a4185e48c6a84a8"},
{file = "winrt_windows_foundation-2.3.0.tar.gz", hash = "sha256:c5766f011c8debbe89b460af4a97d026ca252144e62d7278c9c79c5581ea0c02"},
]
[package.dependencies]
winrt-runtime = "2.3.0"
[package.extras]
all = ["winrt-Windows.Foundation.Collections[all] (==2.3.0)"]
[[package]]
name = "winrt-windows-foundation-collections"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp310-cp310-win32.whl", hash = "sha256:d2fca59eef9582a33c2797b1fda1d5757d66827cc34e6fc1d1c94a5875c4c043"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d14b47d9137aebad71aa4fde5892673f2fa326f5f4799378cb9f6158b07a9824"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:cca5398a4522dffd76decf64a28368cda67e81dc01cad35a9f39cc351af69bdd"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp311-cp311-win32.whl", hash = "sha256:3808af64c95a9b464e8e97f6bec57a8b22168185f1c893f30de69aaf48c85b17"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e9a3842a39feb965545124abfe79ed726adc5a1fc6a192470a3c5d3ec3f7a74"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:751c2a68fef080dfe0af892ef4cebf317844e4baa786e979028757fe2740fba4"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp312-cp312-win32.whl", hash = "sha256:498c1fc403d3dc7a091aaac92af471615de4f9550d544347cb3b169c197183b5"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:4d1b1cacc159f38d8e6b662f6e7a5c41879a36aa7434c1580d7f948c9037419e"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:398d93b76a2cf70d5e75c1f802e1dd856501e63bc9a31f4510ac59f718951b9e"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp313-cp313-win32.whl", hash = "sha256:1e5f1637e0919c7bb5b11ba1eebbd43bc0ad9600cf887b59fcece0f8a6c0eac3"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:c809a70bc0f93d53c7289a0a86d8869740e09fff0c57318a14401f5c17e0b912"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:269942fe86af06293a2676c8b2dcd5cb1d8ddfe1b5244f11c16e48ae0a5d100f"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp39-cp39-win32.whl", hash = "sha256:936b1c5720b564ec699673198addee97f3bdb790622d24c8fd1b346a9767717c"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:905a6ac9cd6b51659a9bba08cf44cfc925f528ef34cdd9c3a6c2632e97804a96"},
{file = "winrt_Windows.Foundation.Collections-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:1d6eac85976bd831e1b8cc479d7f14afa51c27cec5a38e2540077d3400cbd3ef"},
{file = "winrt_windows_foundation_collections-2.3.0.tar.gz", hash = "sha256:15c997fd6b64ef0400a619319ea3c6851c9c24e31d51b6448ba9bac3616d25a0"},
]
[package.dependencies]
winrt-runtime = "2.3.0"
[package.extras]
all = ["winrt-Windows.Foundation[all] (==2.3.0)"]
[[package]]
name = "winrt-windows-storage-streams"
version = "2.3.0"
description = "Python projection of Windows Runtime (WinRT) APIs"
optional = false
python-versions = "<3.14,>=3.9"
groups = ["main"]
markers = "platform_system == \"Windows\" and python_version >= \"3.12\""
files = [
{file = "winrt_Windows.Storage.Streams-2.3.0-cp310-cp310-win32.whl", hash = "sha256:2c0901aee1232e92ed9320644b853d7801a0bdb87790164d56e961cd39910f07"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba07dc25decffd29aa8603119629c167bd03fa274099e3bad331a4920c292b78"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:5b60b48460095c50a00a6f7f9b3b780f5bdcb1ec663fc09458201499f93e23ea"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp311-cp311-win32.whl", hash = "sha256:8388f37759df64ceef1423ae7dd9275c8a6eb3b8245d400173b4916adc94b5ad"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:e5783dbe3694cc3deda594256ebb1088655386959bb834a6bfb7cd763ee87631"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:0a487d19c73b82aafa3d5ef889bb35e6e8e2487ca4f16f5446f2445033d5219c"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp312-cp312-win32.whl", hash = "sha256:272e87e6c74cb2832261ab33db7966a99e7a2400240cc4f8bf526a80ca054c68"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:997bf1a2d52c5f104b172947e571f27d9916a4409b4da592ec3e7f907848dd1a"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:d56daa00205c24ede6669d41eb70d6017e0202371d99f8ee2b0b31350ab59bd5"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp313-cp313-win32.whl", hash = "sha256:7ac4e46fc5e21d8badc5d41779273c3f5e7196f1cf2df1959b6b70eca1d5d85f"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1460027c94c107fcee484997494f3a400f08ee40396f010facb0e72b3b74c457"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:e4553a70f5264a7733596802a2991e2414cdcd5e396b9d11ee87be9abae9329e"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp39-cp39-win32.whl", hash = "sha256:28e1117e23046e499831af16d11f5e61e6066ed6247ef58b93738702522c29b0"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:5511dc578f92eb303aee4d3345ee4ffc88aa414564e43e0e3d84ff29427068f0"},
{file = "winrt_Windows.Storage.Streams-2.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6f5b3f8af4df08f5bf9329373949236ffaef22d021070278795e56da5326a876"},
{file = "winrt_windows_storage_streams-2.3.0.tar.gz", hash = "sha256:d2c010beeb1dd7c135ed67ecfaea13440474a7c469e2e9aa2852db27d2063d44"},
]
[package.dependencies]
winrt-runtime = "2.3.0"
[package.extras]
all = ["winrt-Windows.Foundation.Collections[all] (==2.3.0)", "winrt-Windows.Foundation[all] (==2.3.0)", "winrt-Windows.Storage[all] (==2.3.0)", "winrt-Windows.System[all] (==2.3.0)"]
[extras]
docs = ["Sphinx", "myst-parser", "sphinx-rtd-theme"]
[metadata]
lock-version = "2.1"
python-versions = "^3.11,<3.14"
content-hash = "b8d6cdfca0025197c4ecbc033d9ac2622f8ea9b3fd27dbe4c34c233c8372fce0"
bthome-ble-3.13.0/pyproject.toml 0000664 0000000 0000000 00000004763 15015405444 0016544 0 ustar 00root root 0000000 0000000 [project]
name = "bthome-ble"
version = "3.13.0"
description = "BThome BLE support"
authors = [{ name = "Ernst Klamer", email = "e.klamer@gmail.com" }]
license = "MIT"
readme = "README.md"
requires-python = ">=3.11"
dynamic = ["classifiers", "dependencies", "optional-dependencies"]
[project.urls]
"Repository" = "https://github.com/bluetooth-devices/bthome-ble"
"Documentation" = "https://bthome-ble.readthedocs.io"
"Bug Tracker" = "https://github.com/bluetooth-devices/bthome-ble/issues"
"Changelog" = "https://github.com/bluetooth-devices/bthome-ble/blob/main/CHANGELOG.md"
[tool.poetry]
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries",
]
packages = [
{ include = "bthome_ble", from = "src" },
]
[tool.poetry.dependencies]
python = "^3.11,<3.14"
# Documentation Dependencies
Sphinx = {version = ">=5,<9", optional = true}
sphinx-rtd-theme = {version = ">=1,<4", optional = true}
myst-parser = {version = ">=0.18,<4.1", optional = true}
bluetooth-sensor-state-data = ">=1.6.1"
sensor-state-data = ">=2.16.1"
bluetooth-data-tools = ">=0.1.2"
cryptography = ">=40.0.0"
habluetooth = ">=3.0"
[tool.poetry.extras]
docs = [
"myst-parser",
"sphinx",
"sphinx-rtd-theme",
]
[tool.poetry.group.dev.dependencies]
pytest = "^8.3"
pytest-cov = "^6.1"
[tool.semantic_release]
branch = "main"
version_toml = ["pyproject.toml:project.version"]
version_variable = "src/bthome_ble/__init__.py:__version__"
build_command = "pip install poetry && poetry build"
[tool.pytest.ini_options]
addopts = "-v -Wdefault --cov=bthome_ble --cov-report=term-missing:skip-covered"
pythonpath = ["src"]
[tool.coverage.run]
branch = true
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"@overload",
"if TYPE_CHECKING",
"raise NotImplementedError",
]
[tool.isort]
profile = "black"
known_first_party = ["bthome_ble", "tests"]
[tool.mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
mypy_path = "src/"
no_implicit_optional = true
show_error_codes = true
warn_unreachable = true
warn_unused_ignores = true
exclude = [
'docs/.*',
'setup.py',
]
[[tool.mypy.overrides]]
module = "tests.*"
allow_untyped_defs = true
[[tool.mypy.overrides]]
module = "docs.*"
ignore_errors = true
[build-system]
requires = ["poetry-core>=2.0.0"]
build-backend = "poetry.core.masonry.api"
bthome-ble-3.13.0/renovate.json 0000664 0000000 0000000 00000000101 15015405444 0016324 0 ustar 00root root 0000000 0000000 {
"extends": ["github>browniebroke/renovate-configs:python"]
}
bthome-ble-3.13.0/setup.py 0000664 0000000 0000000 00000000360 15015405444 0015327 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# This is a shim to allow GitHub to detect the package, build is done with poetry
# Taken from https://github.com/Textualize/rich
import setuptools
if __name__ == "__main__":
setuptools.setup(name="bthome-ble")
bthome-ble-3.13.0/src/ 0000775 0000000 0000000 00000000000 15015405444 0014405 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/src/bthome_ble/ 0000775 0000000 0000000 00000000000 15015405444 0016505 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/src/bthome_ble/__init__.py 0000664 0000000 0000000 00000001143 15015405444 0020615 0 ustar 00root root 0000000 0000000 """Parser for BLE advertisements in BTHome format."""
from __future__ import annotations
from sensor_state_data import (
BinarySensorDeviceClass,
DeviceClass,
DeviceKey,
SensorDescription,
SensorDeviceClass,
SensorDeviceInfo,
SensorUpdate,
SensorValue,
Units,
)
from .parser import BTHomeBluetoothDeviceData
__version__ = "3.9.2"
__all__ = [
"BinarySensorDeviceClass",
"BTHomeBluetoothDeviceData",
"DeviceClass",
"DeviceKey",
"SensorDescription",
"SensorDeviceClass",
"SensorDeviceInfo",
"SensorUpdate",
"SensorValue",
"Units",
]
bthome-ble-3.13.0/src/bthome_ble/bthome_v1_encryption.py 0000664 0000000 0000000 00000007025 15015405444 0023221 0 ustar 00root root 0000000 0000000 """Example showing encoding and decoding of BTHome v1 advertisement"""
from __future__ import annotations
import binascii
from cryptography.hazmat.primitives.ciphers.aead import AESCCM
def parse_value(data: bytes) -> dict[str, float]:
"""Parse decrypted payload to readable BTHome data"""
vlength = len(data)
if vlength >= 3:
temp = round(int.from_bytes(data[2:4], "little", signed=False) * 0.01, 2)
humi = round(int.from_bytes(data[6:8], "little", signed=False) * 0.01, 2)
print("Temperature:", temp, "Humidity:", humi)
return {"temperature": temp, "humidity": humi}
print("MsgLength:", vlength, "HexValue:", data.hex())
return {}
def decrypt_payload(
payload: bytes, mic: bytes, key: bytes, nonce: bytes
) -> dict[str, float] | None:
"""Decrypt payload."""
print("Nonce:", nonce.hex())
print("CryptData:", payload.hex())
print("Mic:", mic.hex())
cipher = AESCCM(key, tag_length=4)
print()
print("Starting Decryption data")
try:
data = cipher.decrypt(nonce, payload + mic, b"\x11")
except ValueError as error:
print()
print("Decryption failed:", error)
return None
print("Decryption succeeded, decrypted data:", data.hex())
print()
return parse_value(data=data)
def decrypt_aes_ccm(key: bytes, mac: bytes, data: bytes) -> dict[str, float] | None:
"""Decrypt AES CCM."""
print("MAC:", mac.hex())
print("Bindkey:", key.hex())
adslength = len(data)
if adslength > 15 and data[0] == 0x1E and data[1] == 0x18:
pkt = data[: data[0] + 1]
uuid = pkt[0:2]
encrypted_data = pkt[2:-8]
count_id = pkt[-8:-4]
mic = pkt[-4:]
# nonce: mac [6], uuid16 [2], count_id [4] # 6+2+4 = 12 bytes
nonce = b"".join([mac, uuid, count_id])
return decrypt_payload(encrypted_data, mic, key, nonce)
else:
print("Error: format packet!")
return None
def encrypt_payload(
data: bytes, mac: bytes, uuid16: bytes, count_id: bytes, key: bytes
) -> bytes:
"""Encrypt payload."""
nonce = b"".join([mac, uuid16, count_id]) # 6+2+4 = 12 bytes
cipher = AESCCM(key, tag_length=4)
associated_data = b"\x11"
result = cipher.encrypt(nonce, data, associated_data)
ciphertext = result[:-4]
mic = result[-4:]
print("MAC:", mac.hex())
print("Binkey:", key.hex())
print("Data:", data.hex())
print("Nonce:", nonce.hex())
print("CryptData:", ciphertext.hex(), "Mic:", mic.hex())
return b"".join([uuid16, ciphertext, count_id, mic])
# =============================
# main()
# =============================
def main() -> None:
"""Example to encrypt and decrypt BTHome payload."""
print()
print("====== Test encode -----------------------------------------")
data = bytes(bytearray.fromhex("2302CA090303BF13")) # BTHome data (not encrypted)
parse_value(data) # Print temperature and humidity
print()
print("Preparing data for encryption")
count_id = bytes(bytearray.fromhex("00112233")) # count id (change every message)
mac = binascii.unhexlify("5448E68F80A5") # MAC
uuid16 = b"\x1e\x18"
bindkey = binascii.unhexlify("231d39c1d7cc1ab1aee224cd096db932")
payload = encrypt_payload(
data=data, mac=mac, uuid16=uuid16, count_id=count_id, key=bindkey
)
print()
print("Encrypted data:", payload.hex())
print()
print("====== Test decode -----------------------------------------")
decrypt_aes_ccm(key=bindkey, mac=mac, data=payload)
if __name__ == "__main__":
main()
bthome-ble-3.13.0/src/bthome_ble/bthome_v2_encryption.py 0000664 0000000 0000000 00000007266 15015405444 0023231 0 ustar 00root root 0000000 0000000 """Example showing encoding and decoding of BTHome v2 advertisement"""
from __future__ import annotations
import binascii
from cryptography.hazmat.primitives.ciphers.aead import AESCCM
def parse_value(data: bytes) -> dict[str, float]:
"""Parse decrypted payload to readable BTHome data"""
vlength = len(data)
if vlength >= 3:
temp = round(int.from_bytes(data[1:3], "little", signed=False) * 0.01, 2)
humi = round(int.from_bytes(data[4:6], "little", signed=False) * 0.01, 2)
print("Temperature:", temp, "Humidity:", humi)
return {"temperature": temp, "humidity": humi}
print("MsgLength:", vlength, "HexValue:", data.hex())
return {}
def decrypt_payload(
payload: bytes, mic: bytes, key: bytes, nonce: bytes
) -> dict[str, float] | None:
"""Decrypt payload."""
print("Nonce:", nonce.hex())
print("Ciphertext:", payload.hex())
print("MIC:", mic.hex())
cipher = AESCCM(key, tag_length=4)
print()
print("Starting Decryption data")
try:
data = cipher.decrypt(nonce, payload + mic, None)
except ValueError as error:
print()
print("Decryption failed:", error)
return None
print("Decryption succeeded, decrypted data:", data.hex())
print()
return parse_value(data=data)
def decrypt_aes_ccm(key: bytes, mac: bytes, data: bytes) -> dict[str, float] | None:
"""Decrypt AES CCM."""
print("MAC:", mac.hex())
print("Bindkey:", key.hex())
adslength = len(data)
if adslength > 15 and data[0] == 0xD2 and data[1] == 0xFC:
pkt = data[: data[0] + 1]
uuid = pkt[0:2]
sw_version = pkt[2:3]
encrypted_data = pkt[3:-8]
count_id = pkt[-8:-4]
mic = pkt[-4:]
# nonce: mac [6], uuid16 [2], count_id [4] # 6+3+4 = 13 bytes
nonce = b"".join([mac, uuid, sw_version, count_id])
return decrypt_payload(encrypted_data, mic, key, nonce)
else:
print("Error: format packet!")
return None
def encrypt_payload(
data: bytes,
mac: bytes,
uuid16: bytes,
sw_version: bytes,
count_id: bytes,
key: bytes,
) -> bytes:
"""Encrypt payload."""
nonce = b"".join([mac, uuid16, sw_version, count_id]) # 6+2+1+4 = 13 bytes
cipher = AESCCM(key, tag_length=4)
result = cipher.encrypt(nonce, data, None)
ciphertext = result[:-4]
mic = result[-4:]
print("MAC:", mac.hex())
print("Binkey:", key.hex())
print("Data:", data.hex())
print("Nonce:", nonce.hex())
print("Ciphertext:", ciphertext.hex())
print("MIC:", mic.hex())
return b"".join([uuid16, sw_version, ciphertext, count_id, mic])
# =============================
# main()
# =============================
def main() -> None:
"""Example to encrypt and decrypt BTHome payload."""
print()
print("====== Test encode -----------------------------------------")
data = bytes(bytearray.fromhex("02CA0903BF13")) # BTHome data (not encrypted)
parse_value(data) # Print temperature and humidity
print()
print("Preparing data for encryption")
count_id = bytes(bytearray.fromhex("00112233")) # count id (change every message)
mac = binascii.unhexlify("5448E68F80A5") # MAC
uuid16 = b"\xd2\xfc"
sw_version = b"\x41"
bindkey = binascii.unhexlify("231d39c1d7cc1ab1aee224cd096db932")
payload = encrypt_payload(
data=data,
mac=mac,
uuid16=uuid16,
sw_version=sw_version,
count_id=count_id,
key=bindkey,
)
print()
print("Encrypted data:", payload.hex())
print()
print("====== Test decode -----------------------------------------")
decrypt_aes_ccm(key=bindkey, mac=mac, data=payload)
if __name__ == "__main__":
main()
bthome-ble-3.13.0/src/bthome_ble/const.py 0000664 0000000 0000000 00000033161 15015405444 0020211 0 ustar 00root root 0000000 0000000 """Constants for BTHome measurements."""
import dataclasses
from sensor_state_data import (
BaseDeviceClass,
BinarySensorDeviceClass,
SensorLibrary,
Units,
description,
)
from .event import EventDeviceKeys
@dataclasses.dataclass
class MeasTypes:
meas_format: (
EventDeviceKeys
| description.BaseBinarySensorDescription
| description.BaseSensorDescription
)
data_length: int = 1
data_format: str = "unsigned_integer"
factor: float = 1
class ExtendedSensorDeviceClass(BaseDeviceClass):
"""Device class for additional sensors (compared to sensor-state-data)."""
# Data channel
CHANNEL = "channel"
# Raw hex data
RAW = "raw"
# Text
TEXT = "text"
# Volume storage
VOLUME_STORAGE = "volume_storage"
# Direction
DIRECTION = "direction"
# Precipitation
PRECIPITATION = "precipitation"
class ExtendedSensorLibrary(SensorLibrary):
"""Sensor Library for additional sensors (compared to sensor-state-data)."""
RAW__NONE = description.BaseSensorDescription(
device_class=ExtendedSensorDeviceClass.RAW,
native_unit_of_measurement=None,
)
TEXT__NONE = description.BaseSensorDescription(
device_class=ExtendedSensorDeviceClass.TEXT,
native_unit_of_measurement=None,
)
VOLUME_STORAGE__VOLUME_LITERS = description.BaseSensorDescription(
device_class=ExtendedSensorDeviceClass.VOLUME_STORAGE,
native_unit_of_measurement=Units.VOLUME_LITERS,
)
DIRECTION__DEGREE = description.BaseSensorDescription(
device_class=ExtendedSensorDeviceClass.DIRECTION,
native_unit_of_measurement=Units.DEGREE,
)
PRECIPITATION__LENGTH_MILLIMETERS = description.BaseSensorDescription(
device_class=ExtendedSensorDeviceClass.PRECIPITATION,
native_unit_of_measurement=Units.LENGTH_MILLIMETERS,
)
CHANNEL__NONE = description.BaseSensorDescription(
device_class=ExtendedSensorDeviceClass.CHANNEL,
native_unit_of_measurement=None,
)
MEAS_TYPES: dict[int, MeasTypes] = {
0x00: MeasTypes(meas_format=SensorLibrary.PACKET_ID__NONE),
0x01: MeasTypes(meas_format=SensorLibrary.BATTERY__PERCENTAGE),
0x02: MeasTypes(
meas_format=SensorLibrary.TEMPERATURE__CELSIUS,
data_length=2,
data_format="signed_integer",
factor=0.01,
),
0x03: MeasTypes(
meas_format=SensorLibrary.HUMIDITY__PERCENTAGE,
data_length=2,
factor=0.01,
),
0x04: MeasTypes(
meas_format=SensorLibrary.PRESSURE__MBAR,
data_length=3,
factor=0.01,
),
0x05: MeasTypes(
meas_format=SensorLibrary.LIGHT__LIGHT_LUX,
data_length=3,
factor=0.01,
),
0x06: MeasTypes(
meas_format=SensorLibrary.MASS__MASS_KILOGRAMS,
data_length=2,
factor=0.01,
),
0x07: MeasTypes(
meas_format=SensorLibrary.MASS__MASS_POUNDS,
data_length=2,
factor=0.01,
),
0x08: MeasTypes(
meas_format=SensorLibrary.DEW_POINT__TEMP_CELSIUS,
data_length=2,
data_format="signed_integer",
factor=0.01,
),
0x09: MeasTypes(
meas_format=SensorLibrary.COUNT__NONE,
data_length=1,
),
0x0A: MeasTypes(
meas_format=SensorLibrary.ENERGY__ENERGY_KILO_WATT_HOUR,
data_length=3,
factor=0.001,
),
0x0B: MeasTypes(
meas_format=SensorLibrary.POWER__POWER_WATT,
data_length=3,
factor=0.01,
),
0x0C: MeasTypes(
meas_format=SensorLibrary.VOLTAGE__ELECTRIC_POTENTIAL_VOLT,
data_length=2,
factor=0.001,
),
0x0D: MeasTypes(
meas_format=SensorLibrary.PM25__CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
data_length=2,
),
0x0E: MeasTypes(
meas_format=SensorLibrary.PM10__CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
data_length=2,
),
0x0F: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.GENERIC,
),
),
0x10: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.POWER,
),
),
0x11: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.OPENING,
),
),
0x12: MeasTypes(
meas_format=SensorLibrary.CO2__CONCENTRATION_PARTS_PER_MILLION,
data_length=2,
),
0x13: MeasTypes(
meas_format=(
SensorLibrary.VOLATILE_ORGANIC_COMPOUNDS__CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
),
data_length=2,
),
0x14: MeasTypes(
meas_format=SensorLibrary.MOISTURE__PERCENTAGE,
data_length=2,
factor=0.01,
),
0x15: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.BATTERY,
)
),
0x16: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
)
),
0x17: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.CO,
)
),
0x18: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.COLD,
)
),
0x19: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.CONNECTIVITY,
)
),
0x1A: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.DOOR,
)
),
0x1B: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.GARAGE_DOOR,
)
),
0x1C: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.GAS,
)
),
0x1D: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.HEAT,
)
),
0x1E: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.LIGHT,
)
),
0x1F: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.LOCK,
)
),
0x20: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.MOISTURE,
)
),
0x21: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.MOTION,
)
),
0x22: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.MOVING,
)
),
0x23: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.OCCUPANCY,
)
),
0x24: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.PLUG,
)
),
0x25: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.PRESENCE,
)
),
0x26: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.PROBLEM,
)
),
0x27: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.RUNNING,
)
),
0x28: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.SAFETY,
)
),
0x29: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.SMOKE,
)
),
0x2A: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.SOUND,
)
),
0x2B: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.TAMPER,
)
),
0x2C: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.VIBRATION,
)
),
0x2D: MeasTypes(
meas_format=description.BaseBinarySensorDescription(
device_class=BinarySensorDeviceClass.WINDOW,
)
),
0x2E: MeasTypes(meas_format=SensorLibrary.HUMIDITY__PERCENTAGE),
0x2F: MeasTypes(meas_format=SensorLibrary.MOISTURE__PERCENTAGE),
0x3A: MeasTypes(meas_format=EventDeviceKeys.BUTTON),
0x3C: MeasTypes(
meas_format=EventDeviceKeys.DIMMER,
data_length=2,
),
0x3D: MeasTypes(
meas_format=SensorLibrary.COUNT__NONE,
data_length=2,
),
0x3E: MeasTypes(
meas_format=SensorLibrary.COUNT__NONE,
data_length=4,
),
0x3F: MeasTypes(
meas_format=SensorLibrary.ROTATION__DEGREE,
data_length=2,
data_format="signed_integer",
factor=0.1,
),
0x40: MeasTypes(
meas_format=SensorLibrary.DISTANCE__LENGTH_MILLIMETERS,
data_length=2,
factor=1,
),
0x41: MeasTypes(
meas_format=SensorLibrary.DISTANCE__LENGTH_METERS,
data_length=2,
factor=0.1,
),
0x42: MeasTypes(
meas_format=SensorLibrary.DURATION__TIME_SECONDS,
data_length=3,
factor=0.001,
),
0x43: MeasTypes(
meas_format=SensorLibrary.CURRENT__ELECTRIC_CURRENT_AMPERE,
data_length=2,
factor=0.001,
),
0x44: MeasTypes(
meas_format=SensorLibrary.SPEED__SPEED_METERS_PER_SECOND,
data_length=2,
factor=0.01,
),
0x45: MeasTypes(
meas_format=SensorLibrary.TEMPERATURE__CELSIUS,
data_length=2,
data_format="signed_integer",
factor=0.1,
),
0x46: MeasTypes(
meas_format=SensorLibrary.UV_INDEX__NONE,
data_length=1,
factor=0.1,
),
0x47: MeasTypes(
meas_format=SensorLibrary.VOLUME__VOLUME_LITERS,
data_length=2,
factor=0.1,
),
0x48: MeasTypes(
meas_format=SensorLibrary.VOLUME__VOLUME_MILLILITERS,
data_length=2,
),
0x49: MeasTypes(
meas_format=SensorLibrary.VOLUME_FLOW_RATE__VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR,
data_length=2,
factor=0.001,
),
0x4A: MeasTypes(
meas_format=SensorLibrary.VOLTAGE__ELECTRIC_POTENTIAL_VOLT,
data_length=2,
factor=0.1,
),
0x4B: MeasTypes(
meas_format=SensorLibrary.GAS__VOLUME_CUBIC_METERS,
data_length=3,
factor=0.001,
),
0x4C: MeasTypes(
meas_format=SensorLibrary.GAS__VOLUME_CUBIC_METERS,
data_length=4,
factor=0.001,
),
0x4D: MeasTypes(
meas_format=SensorLibrary.ENERGY__ENERGY_KILO_WATT_HOUR,
data_length=4,
factor=0.001,
),
0x4E: MeasTypes(
meas_format=SensorLibrary.VOLUME__VOLUME_LITERS,
data_length=4,
factor=0.001,
),
0x4F: MeasTypes(
meas_format=SensorLibrary.WATER__VOLUME_LITERS,
data_length=4,
factor=0.001,
),
0x50: MeasTypes(
meas_format=SensorLibrary.TIMESTAMP__NONE,
data_length=4,
data_format="timestamp",
),
0x51: MeasTypes(
meas_format=SensorLibrary.ACCELERATION__ACCELERATION_METERS_PER_SQUARE_SECOND,
data_length=2,
factor=0.001,
),
0x52: MeasTypes(
meas_format=SensorLibrary.GYROSCOPE__GYROSCOPE_DEGREES_PER_SECOND,
data_length=2,
factor=0.001,
),
0x53: MeasTypes(
meas_format=ExtendedSensorLibrary.TEXT__NONE,
data_format="string",
),
0x54: MeasTypes(
meas_format=ExtendedSensorLibrary.RAW__NONE,
data_format="raw",
),
0x55: MeasTypes(
meas_format=ExtendedSensorLibrary.VOLUME_STORAGE__VOLUME_LITERS,
data_length=4,
factor=0.001,
),
0x56: MeasTypes(
meas_format=SensorLibrary.CONDUCTIVITY__CONDUCTIVITY,
data_length=2,
),
0x57: MeasTypes(
meas_format=SensorLibrary.TEMPERATURE__CELSIUS,
data_length=1,
data_format="signed_integer",
),
0x58: MeasTypes(
meas_format=SensorLibrary.TEMPERATURE__CELSIUS,
data_length=1,
data_format="signed_integer",
factor=0.35,
),
0x59: MeasTypes(
meas_format=SensorLibrary.COUNT__NONE,
data_length=1,
data_format="signed_integer",
),
0x5A: MeasTypes(
meas_format=SensorLibrary.COUNT__NONE,
data_length=2,
data_format="signed_integer",
),
0x5B: MeasTypes(
meas_format=SensorLibrary.COUNT__NONE,
data_length=4,
data_format="signed_integer",
),
0x5C: MeasTypes(
meas_format=SensorLibrary.POWER__POWER_WATT,
data_length=4,
factor=0.01,
data_format="signed_integer",
),
0x5D: MeasTypes(
meas_format=SensorLibrary.CURRENT__ELECTRIC_CURRENT_AMPERE,
data_length=2,
factor=0.001,
data_format="signed_integer",
),
0x5E: MeasTypes(
meas_format=ExtendedSensorLibrary.DIRECTION__DEGREE,
data_length=2,
factor=0.01,
),
0x5F: MeasTypes(
meas_format=ExtendedSensorLibrary.PRECIPITATION__LENGTH_MILLIMETERS,
data_length=2,
factor=0.1,
),
0x60: MeasTypes(meas_format=ExtendedSensorLibrary.CHANNEL__NONE),
}
bthome-ble-3.13.0/src/bthome_ble/event.py 0000664 0000000 0000000 00000001242 15015405444 0020177 0 ustar 00root root 0000000 0000000 """Event constants for BTHome measurements."""
from __future__ import annotations
from sensor_state_data.enum import StrEnum
class EventDeviceKeys(StrEnum):
"""Keys for devices that send events."""
# Rocker switch
SWITCH = "switch"
# Dimmer
DIMMER = "dimmer"
# Button
BUTTON = "button"
BUTTON_EVENTS: dict[int, str | None] = {
0x00: None,
0x01: "press",
0x02: "double_press",
0x03: "triple_press",
0x04: "long_press",
0x05: "long_double_press",
0x06: "long_triple_press",
0x80: "hold_press",
}
DIMMER_EVENTS: dict[int, str | None] = {
0x00: None,
0x01: "rotate_left",
0x02: "rotate_right",
}
bthome-ble-3.13.0/src/bthome_ble/parser.py 0000664 0000000 0000000 00000064642 15015405444 0020367 0 ustar 00root root 0000000 0000000 """Parser for BLE advertisements in BTHome format.
This file is shamelessly copied from the following repository:
https://github.com/Ernst79/bleparser/blob/ac8757ad64f1fc17674dcd22111e547cdf2f205b/package/bleparser/ha_ble.py
BTHome was originally developed as HA BLE for ble_monitor and has been renamed
to BTHome for the Home Assistant Bluetooth integration.
MIT License applies.
"""
from __future__ import annotations
import logging
import struct
from datetime import datetime, timezone
from enum import Enum
from typing import Any
from bluetooth_data_tools import short_address
from bluetooth_sensor_state_data import BluetoothData
from cryptography.exceptions import InvalidTag
from cryptography.hazmat.primitives.ciphers.aead import AESCCM
from habluetooth import BluetoothServiceInfoBleak
from sensor_state_data.description import (
BaseBinarySensorDescription,
BaseSensorDescription,
)
from .const import MEAS_TYPES
from .event import BUTTON_EVENTS, DIMMER_EVENTS, EventDeviceKeys
_LOGGER = logging.getLogger(__name__)
class EncryptionScheme(Enum):
# No encryption is needed to use this device
NONE = "none"
# 16 byte encryption key expected
BTHOME_BINDKEY = "bthome_bindkey"
def to_mac(addr: bytes) -> str:
"""Return formatted MAC address."""
return ":".join(f"{i:02X}" for i in addr)
def parse_uint(data_obj: bytes, factor: float = 1.0) -> float:
"""Convert bytes (as unsigned integer) and factor to float."""
decimal_places = -int(f"{factor:e}".split("e")[-1])
return round(
int.from_bytes(data_obj, "little", signed=False) * factor, decimal_places
)
def parse_int(data_obj: bytes, factor: float = 1.0) -> float:
"""Convert bytes (as signed integer) and factor to float."""
decimal_places = -int(f"{factor:e}".split("e")[-1])
return round(
int.from_bytes(data_obj, "little", signed=True) * factor, decimal_places
)
def parse_float(data_obj: bytes, factor: float = 1.0) -> float | None:
"""Convert bytes (as float) and factor to float."""
decimal_places = -int(f"{factor:e}".split("e")[-1])
if len(data_obj) == 2:
[val] = struct.unpack(" str | None:
"""Convert bytes to raw hex string."""
return data_obj.hex()
def parse_string(data_obj: bytes) -> str | None:
"""Convert bytes to string."""
try:
return data_obj.decode("UTF-8")
except UnicodeDecodeError:
_LOGGER.error(
"BTHome data contains bytes that can't be decoded to a string (use UTF-8 encoding)"
)
return None
def parse_timestamp(data_obj: bytes) -> datetime:
"""Convert bytes to a datetime object."""
value = datetime.fromtimestamp(
int.from_bytes(data_obj, "little", signed=False), tz=timezone.utc
)
_LOGGER.error("time %s", value)
return value
def parse_event_type(event_device: str, data_obj: int) -> str | None:
"""Convert bytes to event type."""
if event_device == "dimmer":
event_type = DIMMER_EVENTS.get(data_obj)
elif event_device == "button":
event_type = BUTTON_EVENTS.get(data_obj)
else:
event_type = None
return event_type
def parse_event_properties(
event_device: str, data_obj: bytes
) -> dict[str, str | int | float | None] | None:
"""Convert bytes to event properties."""
if event_device == "dimmer":
# number of steps for rotating a dimmer
return {"steps": int.from_bytes(data_obj, "little", signed=True)}
else:
return None
class BTHomeBluetoothDeviceData(BluetoothData):
"""Data for BTHome Bluetooth devices."""
def __init__(self, bindkey: bytes | None = None) -> None:
super().__init__()
self.set_bindkey(bindkey)
# Data that we know how to parse but don't yet map to the SensorData model.
self.unhandled: dict[str, Any] = {}
# Encryption to expect, based on flags in the UUID.
self.encryption_scheme = EncryptionScheme.NONE
# The encryption counter can be used to verify that the counter of encrypted
# advertisements is increasing, to have some replay protection. We always
# start at zero allow the first message after a restart.
self.encryption_counter = 0.0
# The packet_id is used to filter duplicate messages in BTHome V2.
self.packet_id: float | None = None
# If True then we have used the provided encryption key to decrypt at least
# one payload.
# If False then we have either not seen an encrypted payload, the key is wrong
# or encryption is not in use
self.bindkey_verified = False
# If True then the decryption has failed or has not been verified yet.
# If False then the decryption has succeeded.
self.decryption_failed = True
# If this is True, then we have not seen an advertisement with a payload
# Until we see a payload, we can't tell if this device is encrypted or not
self.pending = True
# The last service_info we saw that had a payload
# We keep this to help in reauth flows where we want to reprocess and old
# value with a new bindkey.
self.last_service_info: BluetoothServiceInfoBleak | None = None
# If this is True, the device is not sending advertisements in a regular interval
self.sleepy_device = False
def set_bindkey(self, bindkey: bytes | None) -> None:
"""Set the bindkey."""
self.bindkey = bindkey
if bindkey:
self.cipher: AESCCM | None = AESCCM(bindkey, tag_length=4)
else:
self.cipher = None
def supported(self, data: BluetoothServiceInfoBleak) -> bool:
if not super().supported(data):
return False
return True
def _start_update(self, service_info: BluetoothServiceInfoBleak) -> None:
"""Update from BLE advertisement data."""
_LOGGER.debug("Parsing BTHome BLE advertisement data: %s", service_info)
for uuid, service_data in service_info.service_data.items():
if uuid in [
"0000181c-0000-1000-8000-00805f9b34fb",
"0000181e-0000-1000-8000-00805f9b34fb",
]:
if self._parse_bthome_v1(service_info, service_data):
self.last_service_info = service_info
elif uuid == "0000fcd2-0000-1000-8000-00805f9b34fb":
if self._parse_bthome_v2(service_info, service_data):
self.last_service_info = service_info
return None
def _parse_bthome_v1(
self, service_info: BluetoothServiceInfoBleak, service_data: bytes
) -> bool:
"""Parser for BTHome sensors version V1"""
identifier = short_address(service_info.address)
name = service_info.name
sw_version = 1
# Remove identifier from ATC sensors.
atc_identifier = (
service_info.address.replace("-", "").replace(":", "")[-6:].upper()
)
if name[-6:] == atc_identifier:
name = name[:-6].rstrip(" _")
# Try to get manufacturer
if name.startswith(("ATC", "LYWSD03MMC")):
manufacturer = "Xiaomi"
elif name.startswith("prst"):
manufacturer = "b-parasite"
name = "b-parasite"
else:
manufacturer = None
if manufacturer:
self.set_device_manufacturer(manufacturer)
self.set_device_name(f"{name} {identifier}")
self.set_title(f"{name} {identifier}")
self.set_device_type("BTHome sensor")
uuid16 = list(service_info.service_data.keys())
if "0000181c-0000-1000-8000-00805f9b34fb" in uuid16:
# Non-encrypted BTHome BLE format
self.encryption_scheme = EncryptionScheme.NONE
self.set_device_sw_version("BTHome BLE v1")
payload = service_data
elif "0000181e-0000-1000-8000-00805f9b34fb" in uuid16:
# Encrypted BTHome BLE format
self.encryption_scheme = EncryptionScheme.BTHOME_BINDKEY
self.set_device_sw_version("BTHome BLE v1 (encrypted)")
mac_readable = service_info.address
source_mac = bytes.fromhex(mac_readable.replace(":", ""))
try:
payload = self._decrypt_bthome(
service_info, service_data, source_mac, sw_version
)
except (ValueError, TypeError):
return True
else:
return False
return self._parse_payload(payload, sw_version, service_info.time)
def _parse_bthome_v2(
self, service_info: BluetoothServiceInfoBleak, service_data: bytes
) -> bool:
"""Parser for BTHome sensors version V2"""
identifier = short_address(service_info.address)
name = service_info.name
if name == service_info.address:
name = "BTHome sensor"
# Remove identifier from ATC sensors name.
atc_identifier = (
service_info.address.replace("-", "").replace(":", "")[-6:].upper()
)
if name[-6:] == atc_identifier:
name = name[:-6].rstrip(" _")
adv_info = service_data[0]
# Determine if encryption is used
encryption = adv_info & (1 << 0) # bit 0
if encryption == 1:
self.encryption_scheme = EncryptionScheme.BTHOME_BINDKEY
else:
self.encryption_scheme = EncryptionScheme.NONE
# If True, the first 6 bytes contain the mac address
mac_included = adv_info & (1 << 1) # bit 1
if mac_included:
bthome_mac_reversed = service_data[1:7]
mac_readable = to_mac(bthome_mac_reversed[::-1])
payload = service_data[7:]
else:
mac_readable = service_info.address
payload = service_data[1:]
# If True, the device is only updating when triggered
self.sleepy_device = bool(adv_info & (1 << 2)) # bit 2
# Check BTHome version
sw_version = (adv_info >> 5) & 7 # 3 bits (5-7)
if sw_version == 2:
if self.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
self.set_device_sw_version(f"BTHome BLE v{sw_version} (encrypted)")
else:
self.set_device_sw_version(f"BTHome BLE v{sw_version}")
else:
_LOGGER.error(
"%s: Sensor is set to use BTHome version %s, which is not existing. "
"Please modify the version in the first byte of the service data",
identifier,
sw_version,
)
return False
# Try to get manufacturer based on the name
if name.startswith(("ATC", "LYWSD03MMC")):
manufacturer = "Xiaomi"
device_type = "Temperature/Humidity sensor"
elif name.startswith("prst"):
manufacturer = "b-parasite"
name = "b-parasite"
device_type = "Plant sensor"
elif name.startswith("SBBT"):
manufacturer = "Shelly"
name = "Shelly BLU Button1"
device_type = "BLU Button1"
elif name.startswith("SBDW"):
manufacturer = "Shelly"
name = "Shelly BLU Door/Window"
device_type = "BLU Door/Window"
else:
manufacturer = None
device_type = "BTHome sensor"
if manufacturer:
self.set_device_manufacturer(manufacturer)
# Get device information from local name and identifier
self.set_device_name(f"{name} {identifier}")
self.set_title(f"{name} {identifier}")
self.set_device_type(device_type)
if self.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
bthome_mac = bytes.fromhex(mac_readable.replace(":", ""))
# Decode encrypted payload
try:
payload = self._decrypt_bthome(
service_info, payload, bthome_mac, sw_version, adv_info
)
except (ValueError, TypeError):
return True
return self._parse_payload(payload, sw_version, service_info.time)
def _skip_old_or_duplicated_advertisement(
self, new_packet_id: float, adv_time: float
) -> bool:
"""
Detect duplicated or older packets
Devices may send duplicated advertisements or advertisements order can change
when passing through a proxy. If more than 4 seconds pass since the last
advertisement assume it is a new packet even if it has the same packet id.
Packet id rollover at 255 to 0, validate that the difference between last packet id
and new packet id is less than 64. This assumes device is not sending more than 16
advertisements per second.
"""
last_packet_id = self.packet_id
# no history, first packet, don't discard packet
if last_packet_id is None or self.last_service_info is None:
_LOGGER.debug(
"%s: First packet, not filtering packet_id %i",
self.title,
new_packet_id,
)
return False
# more than 4 seconds since last packet, don't discard packet
if adv_time - self.last_service_info.time > 4:
_LOGGER.debug(
"%s: Not filtering packet_id, more than 4 seconds since last packet. "
"New time: %i, Old time: %i",
self.title,
adv_time,
self.last_service_info.time,
)
return False
# distance between new packet and old packet is less then 64
if (new_packet_id > last_packet_id and new_packet_id - last_packet_id < 64) or (
new_packet_id < last_packet_id and new_packet_id + 256 - last_packet_id < 64
):
return False
# discard packet (new_packet_id=last_packet_id or older packet)
_LOGGER.debug(
"%s: New packet_id %i indicates an older packet (previous packet_id %i). "
"BLE advertisement will be skipped",
self.title,
new_packet_id,
last_packet_id,
)
return True
def _parse_payload(self, payload: bytes, sw_version: int, adv_time: float) -> bool:
payload_length = len(payload)
next_obj_start = 0
prev_obj_meas_type = 0
result = False
measurements: list[dict[str, Any]] = []
postfix_dict: dict[str, int] = {}
obj_data_format: str | int
# Create a list with all individual objects
while payload_length >= next_obj_start + 1:
obj_start = next_obj_start
if sw_version == 1:
# BTHome V1
obj_meas_type = payload[obj_start + 1]
obj_control_byte = payload[obj_start]
obj_data_length = (obj_control_byte >> 0) & 31 # 5 bits (0-4)
obj_data_format = (obj_control_byte >> 5) & 7 # 3 bits (5-7)
obj_data_start = obj_start + 2
next_obj_start = obj_start + obj_data_length + 1
else:
# BTHome V2
obj_meas_type = payload[obj_start]
if prev_obj_meas_type > obj_meas_type:
_LOGGER.warning(
"%s: BTHome device is not sending object ids in numerical order (from low "
"to high object id). This can cause issues with your BTHome receiver, "
"payload: %s",
self.title,
payload.hex(),
)
if obj_meas_type not in MEAS_TYPES:
_LOGGER.debug(
"%s: Invalid Object ID found in payload: %s",
self.title,
payload.hex(),
)
break
prev_obj_meas_type = obj_meas_type
obj_data_format = MEAS_TYPES[obj_meas_type].data_format
if obj_data_format in ["raw", "string"]:
obj_data_length = payload[obj_start + 1]
obj_data_start = obj_start + 2
else:
obj_data_length = MEAS_TYPES[obj_meas_type].data_length
obj_data_start = obj_start + 1
next_obj_start = obj_data_start + obj_data_length
if obj_data_length == 0:
_LOGGER.debug(
"%s: Invalid payload data length found with length 0, payload: %s",
self.title,
payload.hex(),
)
continue
if payload_length < next_obj_start:
_LOGGER.debug(
"%s: Invalid payload data length, payload: %s",
self.title,
payload.hex(),
)
break
# Filter BLE advertisements with packet_id that has already been parsed.
if obj_meas_type == 0:
new_packet_id = parse_uint(payload[obj_data_start:next_obj_start])
if self._skip_old_or_duplicated_advertisement(new_packet_id, adv_time):
break
self.packet_id = new_packet_id
measurements.append(
{
"data format": obj_data_format,
"data length": obj_data_length,
"measurement type": obj_meas_type,
"measurement data": payload[obj_data_start:next_obj_start],
"device id": None,
}
)
# Get a list of measurement types that are included more than once.
seen_meas_formats = set()
dup_meas_formats = set()
for meas in measurements:
if meas["measurement type"] in MEAS_TYPES:
meas_format = MEAS_TYPES[meas["measurement type"]].meas_format
if meas_format in seen_meas_formats:
dup_meas_formats.add(meas_format)
else:
seen_meas_formats.add(meas_format)
# Parse each object into readable information
for meas in measurements:
if meas["measurement type"] not in MEAS_TYPES:
_LOGGER.debug(
"%s: UNKNOWN measurement type %s in BTHome BLE payload! Adv: %s",
self.title,
meas["measurement type"],
payload.hex(),
)
continue
meas_type = MEAS_TYPES[meas["measurement type"]]
meas_format = meas_type.meas_format
meas_factor = meas_type.factor
if meas_type.meas_format in dup_meas_formats:
# Add a postfix for advertisements with multiple measurements of the same type
postfix_counter = postfix_dict.get(meas_format, 0) + 1
postfix_dict[meas_format] = postfix_counter
postfix = f"_{postfix_counter}"
else:
postfix = ""
value: None | str | int | float | datetime
if meas["data format"] == 0 or meas["data format"] == "unsigned_integer":
value = parse_uint(meas["measurement data"], meas_factor)
elif meas["data format"] == 1 or meas["data format"] == "signed_integer":
value = parse_int(meas["measurement data"], meas_factor)
elif meas["data format"] == 2 or meas["data format"] == "float":
value = parse_float(meas["measurement data"], meas_factor)
elif meas["data format"] == 3 or meas["data format"] == "string":
value = parse_string(meas["measurement data"])
elif meas["data format"] == 4 or meas["data format"] == "raw":
value = parse_raw(meas["measurement data"])
elif meas["data format"] == 5 or meas["data format"] == "timestamp":
value = parse_timestamp(meas["measurement data"])
else:
_LOGGER.error(
"%s: UNKNOWN dataobject in BTHome BLE payload! Adv: %s",
self.title,
payload.hex(),
)
continue
if value is not None:
if (
isinstance(meas_format, BaseSensorDescription)
and meas_format.device_class
):
self.update_sensor(
key=f"{str(meas_format.device_class)}{postfix}",
native_unit_of_measurement=meas_format.native_unit_of_measurement,
native_value=value,
device_class=meas_format.device_class,
)
elif (
isinstance(meas_format, BaseBinarySensorDescription)
and meas_format.device_class
):
self.update_binary_sensor(
key=f"{str(meas_format.device_class)}{postfix}",
device_class=meas_format.device_class,
native_value=bool(value),
)
elif isinstance(meas_format, EventDeviceKeys):
event_type = parse_event_type(
event_device=meas_format,
data_obj=meas["measurement data"][0],
)
event_properties = parse_event_properties(
event_device=meas_format,
data_obj=meas["measurement data"][1:],
)
if event_type:
self.fire_event(
key=f"{str(meas_format)}{postfix}",
event_type=event_type,
event_properties=event_properties,
)
result = True
else:
_LOGGER.debug(
"%s: UNKNOWN dataobject in BTHome BLE payload! Adv: %s",
self.title,
payload.hex(),
)
if not result:
return False
return True
def _decrypt_bthome(
self,
service_info: BluetoothServiceInfoBleak,
service_data: bytes,
bthome_mac: bytes,
sw_version: int,
adv_info: int = 65,
) -> bytes:
"""Decrypt encrypted BTHome BLE advertisements"""
if not self.bindkey:
self.bindkey_verified = False
_LOGGER.debug("%s: Encryption key not set and adv is encrypted", self.title)
raise ValueError
if not self.bindkey or len(self.bindkey) != 16:
self.bindkey_verified = False
_LOGGER.error(
"%s: Encryption key should be 16 bytes (32 characters) long", self.title
)
raise ValueError
# check for minimum length of encrypted advertisement
if len(service_data) < (12 if sw_version == 1 else 11):
_LOGGER.debug(
"%s: Invalid data length (for decryption), adv: %s",
self.title,
service_data.hex(),
)
raise ValueError
# prepare the data for decryption
if sw_version == 1:
uuid = b"\x1e\x18"
else:
uuid = b"\xd2\xfc" + bytes([adv_info])
encrypted_payload = service_data[:-8]
last_encryption_counter = self.encryption_counter
counter = service_data[-8:-4]
new_encryption_counter = parse_uint(counter)
mic = service_data[-4:]
# nonce: mac [6], uuid16 [2 (v1) or 3 (v2)], counter [4]
nonce = b"".join([bthome_mac, uuid, counter])
associated_data = None
if sw_version == 1:
associated_data = b"\x11"
assert self.cipher is not None # nosec
# filter advertisements that are exactly the same as the previous advertisement
if (
self.last_service_info
and service_info.service_data == self.last_service_info.service_data
and self.bindkey_verified is True
):
_LOGGER.debug(
"%s: The service data is the same as the previous service data. Skipping "
"this BLE advertisement.",
self.title,
)
raise ValueError
# Filter advertisements with a decreasing encryption counter.
# Allow cases where the counter has restarted from 0
# (after reaching the highest number or due to a battery change).
# In all other cases, assume the data has been compromised and skip the advertisement.
if (
new_encryption_counter < last_encryption_counter
and self.bindkey_verified is True
and new_encryption_counter >= 100
):
_LOGGER.warning(
"%s: The new encryption counter (%i) is lower than the previous value (%i). "
"The data might be compromised. BLE advertisement will be skipped.",
self.title,
new_encryption_counter,
last_encryption_counter,
)
raise ValueError
# decrypt the data
try:
decrypted_payload = self.cipher.decrypt(
nonce, encrypted_payload + mic, associated_data
)
except InvalidTag as error:
if self.decryption_failed is True:
# we only ask for reautentification after the decryption has failed twice.
self.bindkey_verified = False
else:
self.decryption_failed = True
_LOGGER.warning("%s: Decryption failed: %s", self.title, error)
_LOGGER.debug("%s: mic: %s", self.title, mic.hex())
_LOGGER.debug("%s: nonce: %s", self.title, nonce.hex())
_LOGGER.debug(
"%s: encrypted_payload: %s", self.title, encrypted_payload.hex()
)
raise ValueError
if decrypted_payload is None:
self.bindkey_verified = False
_LOGGER.error(
"%s: Decryption failed for %s, decrypted payload is None",
self.title,
to_mac(bthome_mac),
)
raise ValueError
self.decryption_failed = False
self.bindkey_verified = True
self.encryption_counter = new_encryption_counter
return decrypted_payload
bthome-ble-3.13.0/src/bthome_ble/py.typed 0000664 0000000 0000000 00000000000 15015405444 0020172 0 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/templates/ 0000775 0000000 0000000 00000000000 15015405444 0015614 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/templates/CHANGELOG.md.j2 0000664 0000000 0000000 00000001235 15015405444 0017740 0 ustar 00root root 0000000 0000000 # Changelog
{%- for version, release in context.history.released.items() %}
## {{ version.as_tag() }} ({{ release.tagged_date.strftime("%Y-%m-%d") }})
{%- for category, commits in release["elements"].items() %}
{# Category title: Breaking, Fix, Documentation #}
### {{ category | capitalize }}
{# List actual changes in the category #}
{%- for commit in commits %}
{% if commit is not none and commit.descriptions is defined %}
- {{ commit.descriptions[0] | capitalize }} ([`{{ commit.short_hash }}`]({{ commit.hexsha | commit_hash_url }}))
{% endif %}
{%- endfor %}{# for commit #}
{%- endfor %}{# for category, commits #}
{%- endfor %}{# for version, release #}
bthome-ble-3.13.0/tests/ 0000775 0000000 0000000 00000000000 15015405444 0014760 5 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/tests/__init__.py 0000664 0000000 0000000 00000000000 15015405444 0017057 0 ustar 00root root 0000000 0000000 bthome-ble-3.13.0/tests/test_parser_v1.py 0000664 0000000 0000000 00000130060 15015405444 0020273 0 ustar 00root root 0000000 0000000 """Tests for the parser of BLE advertisements in BTHome V1 format."""
import logging
from datetime import datetime, timezone
from unittest.mock import patch
import pytest
from bluetooth_sensor_state_data import SensorUpdate
from habluetooth import BluetoothServiceInfoBleak
from sensor_state_data import (
BinarySensorDescription,
BinarySensorDeviceClass,
BinarySensorValue,
DeviceKey,
Event,
SensorDescription,
SensorDeviceClass,
SensorDeviceInfo,
SensorValue,
Units,
)
from bthome_ble.parser import BTHomeBluetoothDeviceData, EncryptionScheme
ADVERTISEMENT_TIME = 1709331995.5181565
KEY_BATTERY = DeviceKey(key="battery", device_id=None)
KEY_BINARY_GENERIC = DeviceKey(key="generic", device_id=None)
KEY_BINARY_OPENING = DeviceKey(key="opening", device_id=None)
KEY_BINARY_POWER = DeviceKey(key="power", device_id=None)
KEY_BUTTON = DeviceKey(key="button", device_id=None)
KEY_CO2 = DeviceKey(key="carbon_dioxide", device_id=None)
KEY_DIMMER = DeviceKey(key="dimmer", device_id=None)
KEY_COUNT = DeviceKey(key="count", device_id=None)
KEY_DEW_POINT = DeviceKey(key="dew_point", device_id=None)
KEY_ENERGY = DeviceKey(key="energy", device_id=None)
KEY_HUMIDITY = DeviceKey(key="humidity", device_id=None)
KEY_ILLUMINANCE = DeviceKey(key="illuminance", device_id=None)
KEY_MASS = DeviceKey(key="mass", device_id=None)
KEY_MOISTURE = DeviceKey(key="moisture", device_id=None)
KEY_PACKET_ID = DeviceKey(key="packet_id", device_id=None)
KEY_PM25 = DeviceKey(key="pm25", device_id=None)
KEY_PM10 = DeviceKey(key="pm10", device_id=None)
KEY_POWER = DeviceKey(key="power", device_id=None)
KEY_PRESSURE = DeviceKey(key="pressure", device_id=None)
KEY_SIGNAL_STRENGTH = DeviceKey(key="signal_strength", device_id=None)
KEY_TEMPERATURE = DeviceKey(key="temperature", device_id=None)
KEY_TIMESTAMP = DeviceKey(key="timestamp", device_id=None)
KEY_VOC = DeviceKey(key="volatile_organic_compounds", device_id=None)
KEY_VOLTAGE = DeviceKey(key="voltage", device_id=None)
@pytest.fixture(autouse=True)
def logging_config(caplog):
caplog.set_level(logging.DEBUG)
@pytest.fixture(autouse=True)
def mock_platform():
with patch("sys.platform") as p:
p.return_value = "linux"
yield p
def bytes_to_service_info(
payload: bytes, local_name: str, address: str = "00:00:00:00:00:00"
) -> BluetoothServiceInfoBleak:
"""Convert bytes to service info"""
return BluetoothServiceInfoBleak(
name=local_name,
address=address,
rssi=-60,
manufacturer_data={},
service_data={"0000181c-0000-1000-8000-00805f9b34fb": payload},
service_uuids=["0000181c-0000-1000-8000-00805f9b34fb"],
source="",
device=None,
advertisement=None,
connectable=False,
time=ADVERTISEMENT_TIME,
tx_power=None,
)
def bytes_to_encrypted_service_info(
payload: bytes, local_name: str, address: str = "00:00:00:00:00:00"
) -> BluetoothServiceInfoBleak:
"""Convert bytes to service info"""
return BluetoothServiceInfoBleak(
name=local_name,
address=address,
rssi=-60,
manufacturer_data={},
service_data={"0000181e-0000-1000-8000-00805f9b34fb": payload},
service_uuids=["0000181e-0000-1000-8000-00805f9b34fb"],
source="",
device=None,
advertisement=None,
connectable=False,
time=ADVERTISEMENT_TIME,
tx_power=None,
)
def test_can_create():
BTHomeBluetoothDeviceData()
def test_encryption_key_needed():
"""Test that we can detect that an encryption key is needed."""
data_string = b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99'
advertisement = bytes_to_encrypted_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert device.supported(advertisement)
assert device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY
assert not device.bindkey_verified
def test_encryption_no_key_needed():
"""Test that we can detect that no encryption key is needed."""
data_string = b"\x02\x00\x0c\x04\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert device.supported(advertisement)
assert device.encryption_scheme == EncryptionScheme.NONE
assert not device.bindkey_verified
def test_bindkey_wrong():
"""Test BTHome parser with wrong encryption key."""
bindkey = "814aac74c4f17b6c1581e1ab87816b99"
data_string = b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99'
advertisement = bytes_to_encrypted_service_info(
data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert not device.bindkey_verified
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="BTHome sensor",
sw_version="BTHome BLE v1 (encrypted)",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60
),
},
)
def test_bindkey_correct():
"""Test BTHome parser with correct encryption key."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99'
advertisement = bytes_to_encrypted_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 80A5",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 80A5",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1 (encrypted)",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.06
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=50.55
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bindkey_verified_can_be_unset():
"""Test BTHome parser with wrong encryption key."""
bindkey = "814aac74c4f17b6c1581e1ab87816b99"
data_string = b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99'
advertisement = bytes_to_encrypted_service_info(
data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
device.bindkey_verified = True
assert device.supported(advertisement)
assert not device.bindkey_verified
def test_bthome_temperature_humidity(caplog):
"""Test BTHome parser for temperature humidity reading without encryption."""
data_string = b"#\x02\xca\t\x03\x03\xbf\x13"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.06
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=50.55
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_temperature_humidity_battery(caplog):
"""Test BTHome parser for temperature humidity battery reading."""
data_string = b"#\x02]\t\x03\x03\xb7\x18\x02\x01]"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=23.97
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=63.27
),
KEY_BATTERY: SensorValue(
device_key=KEY_BATTERY, name="Battery", native_value=93
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_pressure(caplog):
"""Test BTHome parser for pressure reading without encryption."""
data_string = b"\x04\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_PRESSURE: SensorDescription(
device_key=KEY_PRESSURE,
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=Units.PRESSURE_MBAR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PRESSURE: SensorValue(
device_key=KEY_PRESSURE, name="Pressure", native_value=1008.83
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_illuminance(caplog):
"""Test BTHome parser for illuminance reading without encryption."""
data_string = b"\x04\x05\x13\x8a\x14"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_ILLUMINANCE: SensorDescription(
device_key=KEY_ILLUMINANCE,
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=Units.LIGHT_LUX,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_ILLUMINANCE: SensorValue(
device_key=KEY_ILLUMINANCE, name="Illuminance", native_value=13460.67
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_mass_kilograms(caplog):
"""Test BTHome parser for mass reading in kilograms without encryption."""
data_string = b"\x43\x06\xfe\x70"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_MASS: SensorDescription(
device_key=KEY_MASS,
device_class=SensorDeviceClass.MASS,
native_unit_of_measurement=Units.MASS_KILOGRAMS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_MASS: SensorValue(
device_key=KEY_MASS, name="Mass", native_value=102.24
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_mass_pounds(caplog):
"""Test BTHome parser for mass reading in pounds without encryption."""
data_string = b"\x03\x07\x3e\x1d"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_MASS: SensorDescription(
device_key=KEY_MASS,
device_class=SensorDeviceClass.MASS,
native_unit_of_measurement=Units.MASS_POUNDS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_MASS: SensorValue(device_key=KEY_MASS, name="Mass", native_value=74.86),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_dew_point(caplog):
"""Test BTHome parser for dew point reading without encryption."""
data_string = b"\x23\x08\xca\x06"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_DEW_POINT: SensorDescription(
device_key=KEY_DEW_POINT,
device_class=SensorDeviceClass.DEW_POINT,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_DEW_POINT: SensorValue(
device_key=KEY_DEW_POINT, name="Dew Point", native_value=17.38
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_count(caplog):
"""Test BTHome parser for counter reading without encryption."""
data_string = b"\x02\x09\x60"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_COUNT: SensorDescription(
device_key=KEY_COUNT,
device_class=SensorDeviceClass.COUNT,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_COUNT: SensorValue(device_key=KEY_COUNT, name="Count", native_value=96),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_energy(caplog):
"""Test BTHome parser for energy reading without encryption."""
data_string = b"\x04\n\x13\x8a\x14"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_ENERGY: SensorDescription(
device_key=KEY_ENERGY,
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=Units.ENERGY_KILO_WATT_HOUR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_ENERGY: SensorValue(
device_key=KEY_ENERGY, name="Energy", native_value=1346.067
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_power(caplog):
"""Test BTHome parser for power reading without encryption."""
data_string = b"\x04\x0b\x02\x1b\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_POWER: SensorDescription(
device_key=KEY_POWER,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=Units.POWER_WATT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_POWER: SensorValue(
device_key=KEY_POWER, name="Power", native_value=69.14
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_voltage(caplog):
"""Test BThome parser for voltage reading without encryption."""
data_string = b"\x03\x0c\x02\x0c"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLTAGE: SensorDescription(
device_key=KEY_VOLTAGE,
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLTAGE: SensorValue(
device_key=KEY_VOLTAGE, name="Voltage", native_value=3.074
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_binary_sensor(caplog):
"""Test BTHome parser for binary sensor without device class, without encryption."""
data_string = b"\x02\x0f\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_GENERIC: BinarySensorDescription(
device_key=KEY_BINARY_GENERIC,
device_class=BinarySensorDeviceClass.GENERIC,
),
},
binary_entity_values={
KEY_BINARY_GENERIC: BinarySensorValue(
device_key=KEY_BINARY_GENERIC, name="Generic", native_value=True
),
},
)
def test_bthome_binary_sensor_power(caplog):
"""Test BTHome parser for binary sensor power without encryption."""
data_string = b"\x02\x10\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_POWER: BinarySensorDescription(
device_key=KEY_BINARY_POWER,
device_class=BinarySensorDeviceClass.POWER,
),
},
binary_entity_values={
KEY_BINARY_POWER: BinarySensorValue(
device_key=KEY_BINARY_POWER, name="Power", native_value=True
),
},
)
def test_bthome_binary_sensor_opening(caplog):
"""Test BTHome parser for binary sensor opening without encryption."""
data_string = b"\x02\x11\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_OPENING: BinarySensorDescription(
device_key=KEY_BINARY_OPENING,
device_class=BinarySensorDeviceClass.OPENING,
),
},
binary_entity_values={
KEY_BINARY_OPENING: BinarySensorValue(
device_key=KEY_BINARY_OPENING, name="Opening", native_value=False
),
},
)
def test_bthome_pm(caplog):
"""Test BTHome parser for PM2.5 and PM10 reading without encryption."""
data_string = b"\x03\r\x12\x0c\x03\x0e\x02\x1c"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_PM25: SensorDescription(
device_key=KEY_PM25,
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=(
Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
),
),
KEY_PM10: SensorDescription(
device_key=KEY_PM10,
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=(
Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
),
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PM25: SensorValue(device_key=KEY_PM25, name="Pm25", native_value=3090),
KEY_PM10: SensorValue(device_key=KEY_PM10, name="Pm10", native_value=7170),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_co2(caplog):
"""Test BTHome parser for CO2 reading without encryption."""
data_string = b"\x03\x12\xe2\x04"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_CO2: SensorDescription(
device_key=KEY_CO2,
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=Units.CONCENTRATION_PARTS_PER_MILLION,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_CO2: SensorValue(
device_key=KEY_CO2, name="Carbon Dioxide", native_value=1250
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_timestamp(caplog):
"""Test BTHome parser for Unix timestamp (seconds from 1-1-1970)."""
data_string = b"\xa5\x50\x5d\x39\x61\x64"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_TIMESTAMP: SensorDescription(
device_key=KEY_TIMESTAMP,
device_class=SensorDeviceClass.TIMESTAMP,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TIMESTAMP: SensorValue(
device_key=KEY_TIMESTAMP,
name="Timestamp",
native_value=datetime(2023, 5, 14, 19, 41, 17, tzinfo=timezone.utc),
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_voc(caplog):
"""Test BTHome parser for VOC reading without encryption."""
data_string = b"\x03\x133\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_VOC: SensorDescription(
device_key=KEY_VOC,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOC: SensorValue(
device_key=KEY_VOC, name="Volatile Organic Compounds", native_value=307
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_moisture(caplog):
"""Test BTHome parser for moisture reading from b-parasite sensor."""
data_string = b"\x03\x14\x02\x0c"
advertisement = bytes_to_service_info(
data_string, local_name="prst", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="b-parasite 18B2",
devices={
None: SensorDeviceInfo(
name="b-parasite 18B2",
manufacturer="b-parasite",
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_MOISTURE: SensorDescription(
device_key=KEY_MOISTURE,
device_class=SensorDeviceClass.MOISTURE,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_MOISTURE: SensorValue(
device_key=KEY_MOISTURE, name="Moisture", native_value=30.74
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_event_button_long_press(caplog):
"""Test BTHome parser for an event of a long press on a button without encryption."""
data_string = b"\x02\x3a\x04"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={
KEY_BUTTON: Event(
device_key=KEY_BUTTON,
name="Button",
event_type="long_press",
event_properties=None,
),
},
)
def test_bthome_event_dimmer_rotate_left_3_steps(caplog):
"""Test BTHome parser for an event rotating a dimmer 3 steps left."""
data_string = b"\x03\x3c\x01\x03"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={
KEY_DIMMER: Event(
device_key=KEY_DIMMER,
name="Dimmer",
event_type="rotate_left",
event_properties={"steps": 3},
),
},
)
def test_bthome_multiple_uuids(caplog):
"""Test BTHome parser for a device that broadcasts multiple uuids."""
advertisement = BluetoothServiceInfoBleak(
name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
rssi=-60,
manufacturer_data={},
service_data={
"0000181a-0000-1000-8000-00805f9b34fb": b"\xc4$\x818\xc1\xa4V\x08\x83\x18\xbf",
"0000fe95-0000-1000-8000-00805f9b34fb": b"0X[\x05\x01\xc4$\x818\xc1\xa4(\x01\x00",
"0000181c-0000-1000-8000-00805f9b34fb": b"\x02\x00\xb4\x02\x10\x00\x03\x0c\xbe\x0b",
},
service_uuids=[
"0000181a-0000-1000-8000-00805f9b34fb",
"0000fe95-0000-1000-8000-00805f9b34fb",
"0000181c-0000-1000-8000-00805f9b34fb",
],
source="",
device=None,
advertisement=None,
connectable=False,
time=ADVERTISEMENT_TIME,
tx_power=None,
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
KEY_VOLTAGE: SensorDescription(
device_key=KEY_VOLTAGE,
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=180
),
KEY_VOLTAGE: SensorValue(
device_key=KEY_VOLTAGE, name="Voltage", native_value=3.006
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
DeviceKey(key="power", device_id=None): BinarySensorDescription(
device_key=DeviceKey(key="power", device_id=None),
device_class=BinarySensorDeviceClass.POWER,
)
},
binary_entity_values={
DeviceKey(key="power", device_id=None): BinarySensorValue(
device_key=DeviceKey(key="power", device_id=None),
name="Power",
native_value=False,
)
},
)
bthome-ble-3.13.0/tests/test_parser_v2.py 0000664 0000000 0000000 00000436164 15015405444 0020312 0 ustar 00root root 0000000 0000000 """Tests for the parser of BLE advertisements in BTHome V2 format."""
import logging
from datetime import datetime, timezone
from unittest.mock import patch
import pytest
from bluetooth_sensor_state_data import SensorUpdate
from habluetooth import BluetoothServiceInfoBleak
from sensor_state_data import (
BinarySensorDescription,
BinarySensorDeviceClass,
BinarySensorValue,
DeviceKey,
Event,
SensorDescription,
SensorDeviceClass,
SensorDeviceInfo,
SensorValue,
Units,
)
from bthome_ble.const import ExtendedSensorDeviceClass
from bthome_ble.parser import BTHomeBluetoothDeviceData, EncryptionScheme
ADVERTISEMENT_TIME = 1709331995.5181565
KEY_ACCELERATION = DeviceKey(key="acceleration", device_id=None)
KEY_BATTERY = DeviceKey(key="battery", device_id=None)
KEY_BINARY_GENERIC = DeviceKey(key="generic", device_id=None)
KEY_BINARY_OPENING = DeviceKey(key="opening", device_id=None)
KEY_BINARY_POWER = DeviceKey(key="power", device_id=None)
KEY_BINARY_WINDOW = DeviceKey(key="window", device_id=None)
KEY_BUTTON = DeviceKey(key="button", device_id=None)
KEY_CO2 = DeviceKey(key="carbon_dioxide", device_id=None)
KEY_DIMMER = DeviceKey(key="dimmer", device_id=None)
KEY_DISTANCE = DeviceKey(key="distance", device_id=None)
KEY_COUNT = DeviceKey(key="count", device_id=None)
KEY_CURRENT = DeviceKey(key="current", device_id=None)
KEY_DEW_POINT = DeviceKey(key="dew_point", device_id=None)
KEY_DURATION = DeviceKey(key="duration", device_id=None)
KEY_ENERGY = DeviceKey(key="energy", device_id=None)
KEY_GAS = DeviceKey(key="gas", device_id=None)
KEY_GYROSCOPE = DeviceKey(key="gyroscope", device_id=None)
KEY_HUMIDITY = DeviceKey(key="humidity", device_id=None)
KEY_ILLUMINANCE = DeviceKey(key="illuminance", device_id=None)
KEY_MASS = DeviceKey(key="mass", device_id=None)
KEY_MOISTURE = DeviceKey(key="moisture", device_id=None)
KEY_PACKET_ID = DeviceKey(key="packet_id", device_id=None)
KEY_PM25 = DeviceKey(key="pm25", device_id=None)
KEY_PM10 = DeviceKey(key="pm10", device_id=None)
KEY_POWER = DeviceKey(key="power", device_id=None)
KEY_PRESSURE = DeviceKey(key="pressure", device_id=None)
KEY_RAW = DeviceKey(key="raw", device_id=None)
KEY_ROTATION = DeviceKey(key="rotation", device_id=None)
KEY_SIGNAL_STRENGTH = DeviceKey(key="signal_strength", device_id=None)
KEY_SPEED = DeviceKey(key="speed", device_id=None)
KEY_TEMPERATURE = DeviceKey(key="temperature", device_id=None)
KEY_TEXT = DeviceKey(key="text", device_id=None)
KEY_TIMESTAMP = DeviceKey(key="timestamp", device_id=None)
KEY_UV_INDEX = DeviceKey(key="uv_index", device_id=None)
KEY_VOC = DeviceKey(key="volatile_organic_compounds", device_id=None)
KEY_VOLTAGE = DeviceKey(key="voltage", device_id=None)
KEY_VOLUME = DeviceKey(key="volume", device_id=None)
KEY_VOLUME_FLOW_RATE = DeviceKey(key="volume_flow_rate", device_id=None)
KEY_VOLUME_STORAGE = DeviceKey(key="volume_storage", device_id=None)
KEY_WATER = DeviceKey(key="water", device_id=None)
KEY_CONDUCTIVITY = DeviceKey(key="conductivity", device_id=None)
@pytest.fixture(autouse=True)
def logging_config(caplog):
caplog.set_level(logging.DEBUG)
@pytest.fixture(autouse=True)
def mock_platform():
with patch("sys.platform") as p:
p.return_value = "linux"
yield p
def bytes_to_service_info(
payload: bytes,
local_name: str,
address: str = "00:00:00:00:00:00",
time: float = ADVERTISEMENT_TIME,
) -> BluetoothServiceInfoBleak:
"""Convert bytes to service info"""
return BluetoothServiceInfoBleak(
name=local_name,
address=address,
rssi=-60,
manufacturer_data={},
service_data={"0000fcd2-0000-1000-8000-00805f9b34fb": payload},
service_uuids=[],
source="",
device=None,
advertisement=None,
connectable=False,
time=time,
tx_power=None,
)
def test_can_create():
BTHomeBluetoothDeviceData()
def test_encryption_key_needed():
"""Test that we can detect that an encryption key is needed."""
data_string = (
b"\x41\xd2\xfc\xef\xe5\x2e\xb2\x12\xd6\x00\x11\x22\x33\xbc\x38\xc9\x66"
)
advertisement = bytes_to_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert device.supported(advertisement)
assert device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY
assert not device.bindkey_verified
def test_encryption_no_key_needed():
"""Test that we can detect that no encryption key is needed."""
data_string = b"\x40\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert device.supported(advertisement)
assert device.encryption_scheme == EncryptionScheme.NONE
assert not device.bindkey_verified
def test_sleepy_device():
"""Test that we can detect that a device that doesn't update regularly."""
data_string = b"\x44\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert device.supported(advertisement)
assert device.sleepy_device
def test_no_sleepy_device():
"""Test that we can detect that a device that updates regularly."""
data_string = b"\x40\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert device.supported(advertisement)
assert not device.sleepy_device
def test_mac_as_name():
"""
A sensor without a name gets its MAC address as name from BluetoothServiceInfo.
Test that this sensor has BTHome sensor + identifier as its name.
"""
data_string = b"\x40\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="A4:C1:38:8D:18:B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert device.supported(advertisement)
assert device.update(advertisement) == SensorUpdate(
title="BTHome sensor 18B2",
devices={
None: SensorDeviceInfo(
name="BTHome sensor 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PRESSURE: SensorDescription(
device_key=KEY_PRESSURE,
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=Units.PRESSURE_MBAR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PRESSURE: SensorValue(
device_key=KEY_PRESSURE, name="Pressure", native_value=1008.83
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_has_incorrect_version():
"""Test that we can detect a non-existing version v7."""
data_string = b"\xe1\x02\x00\x0c\x04\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="A4:C1:38:8D:18:B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData()
assert not device.supported(advertisement)
def test_bindkey_wrong():
"""Test BTHome parser with wrong encryption key."""
bindkey = "814aac74c4f17b6c1581e1ab87816b99"
data_string = (
b"\x41\xd2\xfc\xef\xe5\x2e\xb2\x12\xd6\x00\x11\x22\x33\xbc\x38\xc9\x66"
)
advertisement = bytes_to_service_info(
data_string,
local_name="Test Sensor",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert not device.bindkey_verified
assert device.decryption_failed
assert device.update(advertisement) == SensorUpdate(
title="Test Sensor 18B2",
devices={
None: SensorDeviceInfo(
name="Test Sensor 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2 (encrypted)",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60
),
},
)
def test_bindkey_correct():
"""Test BTHome parser with correct encryption key."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b"\x41\xa4\x72\x66\xc9\x5f\x73\x00\x11\x22\x33\x78\x23\x72\x14"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert not device.decryption_failed
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 80A5",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 80A5",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2 (encrypted)",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.06
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=50.55
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_incorrect_bindkey_length(caplog):
"""Test BTHome parser with incorrect encryption key length."""
bindkey = (
"231d39c1d7cc1ab1aee224cd096db932231d39c1d7cc1ab1" # 24 bytes instead of 16
)
data_string = b"\x41\xa4\x72\x66\xc9\x5f\x73\x00\x11\x22\x33\x78\x23\x72\x14"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert not device.bindkey_verified
assert device.decryption_failed
assert (
"TEST DEVICE 80A5: Encryption key should be 16 bytes (32 characters) long"
in caplog.text
)
def test_bindkey_set_late():
"""Test BTHome parser with correct encryption key set after the device is created."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b"\x41\xa4\x72\x66\xc9\x5f\x73\x00\x11\x22\x33\x78\x23\x72\x14"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData()
device.set_bindkey(bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 80A5",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 80A5",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2 (encrypted)",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.06
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=50.55
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bindkey_verified_can_be_unset():
"""Test BTHome parser with wrong encryption key."""
bindkey = "814aac74c4f17b6c1581e1ab87816b99"
data_string = b"\x41\xa4\x72\x66\xc9\x5f\x73\x00\x11\x22\x33\x78\x23\x72\x14"
advertisement = bytes_to_service_info(
data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
device.bindkey_verified = True
device.decryption_failed = False
assert device.supported(advertisement)
# the first advertisement will fail decryption, but we don't ask to reauth yet
assert device.bindkey_verified
assert device.decryption_failed
data_string = b"\x41\xa4\x72\x66\xc9\x5f\x73\x01\x11\x22\x33\x78\x23\x72\x14"
advertisement = bytes_to_service_info(
data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)
assert device.supported(advertisement)
# the second advertisement will fail decryption again, but now we ask to reauth
assert device.decryption_failed
assert not device.bindkey_verified
def test_same_service_data(caplog):
"""Test BTHome parser with the same service data."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b"\x41\xe4\x45\xf3\xc9\x96\x2b\x33\x22\x11\x00\x6c\x7c\x45\x19"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.encryption_counter == 1122867
data_string = b"\x41\xe4\x45\xf3\xc9\x96\x2b\x33\x22\x11\x00\x6c\x7c\x45\x19"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.encryption_counter == 1122867
assert (
"TEST DEVICE 80A5: The service data is the same as the previous service data. "
"Skipping this BLE advertisement." in caplog.text
)
def test_increasing_encryption_counter(caplog):
"""Test BTHome parser with increasing encryption counter."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b"\x41\xe4\x45\xf3\xc9\x96\x2b\x33\x22\x11\x00\x6c\x7c\x45\x19"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.encryption_counter == 1122867
data_string = b"\x41\x3e\x93\x2c\xc7\x17\x5f\x34\x22\x11\x00\x55\x38\x76\xaf"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.encryption_counter == 1122868
def test_decreasing_encryption_counter(caplog):
"""Test BTHome parser with decreasing encryption counter."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b"\x41\xe4\x45\xf3\xc9\x96\x2b\x33\x22\x11\x00\x6c\x7c\x45\x19"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.encryption_counter == 1122867
data_string = b"\x41\x72\x3d\x30\x35\xfb\x88\x32\x22\x11\x00\x9e\x74\x14\xc0"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
assert device.supported(advertisement)
assert device.bindkey_verified
# encryption counter should not be updated as it is lower
assert device.encryption_counter == 1122867
assert (
"TEST DEVICE 80A5: The new encryption counter (1122866) is lower than the previous value "
"(1122867). The data might be compromised. BLE advertisement will be skipped."
in caplog.text
)
def test_reset_encryption_counter(caplog):
"""Test BTHome parser during reset of the encryption counter."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b"\x41\xba\x0d\xb0\x7a\xee\xf6\xff\xff\xff\xff\x21\xfd\x46\x00"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.encryption_counter == 4294967295
data_string = b"\x41\xde\x40\xb5\x0e\x67\x66\x00\x00\x00\x00\xd7\xcb\x95\xde"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.encryption_counter == 0
def test_too_short_encryption_advertisement(caplog):
"""Test BTHome parser with a too short encrypted advertisement."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data_string = b"\x41\xba\x0d\xb0\x7a\xee\xf6\x00"
advertisement = bytes_to_service_info(
data_string,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert "TEST DEVICE 80A5: Invalid data length (for decryption), adv:" in caplog.text
def test_identical_packet_id(caplog):
"""Test BTHome parser for skipping BLE advertisement with identical or older packet id."""
# 1st advertisement
data_string = b"\x40\x00\x09"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=9
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
assert device.packet_id == 9
assert "ATC 18B2: First packet, not filtering packet_id 9" in caplog.text
# advertisement with the same packet id
device.update(advertisement)
assert device.packet_id == 9
assert (
"ATC 18B2: New packet_id 9 indicates an older packet (previous packet_id 9). "
"BLE advertisement will be skipped" in caplog.text
)
# advertisement packet id decreased by one, should be dropped
data_string_2 = b"\x40\x00\x08"
advertisement_2 = bytes_to_service_info(
data_string_2, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
assert device.update(advertisement_2)
assert device.packet_id == 9
assert (
"ATC 18B2: New packet_id 8 indicates an older packet (previous packet_id 9). "
"BLE advertisement will be skipped" in caplog.text
)
# advertisement more than 4 seconds from last advertisement
advertisement_3 = bytes_to_service_info(
data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
time=ADVERTISEMENT_TIME + 5,
)
assert device.update(advertisement_3)
assert device.packet_id == 9
assert (
"ATC 18B2: Not filtering packet_id, more than 4 seconds since last packet."
in caplog.text
)
def test_increasing_packet_id(caplog):
"""Test BTHome parser for BLE advertisement with increasing packet id."""
# start with 189
data_string = b"\x40\x00\xbd"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
device.update(advertisement)
assert device.packet_id == 189
# increase by 1 to 190
data_string_2 = b"\x40\x00\xbe"
advertisement_2 = bytes_to_service_info(
data_string_2, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
assert device.update(advertisement_2) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=190
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
assert device.packet_id == 190
# increast by 63 to 253
data_string_3 = b"\x40\x00\xfd"
advertisement_3 = bytes_to_service_info(
data_string_3, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device.update(advertisement_3)
assert device.packet_id == 253
# increast by 50, rollover to 47
data_string_4 = b"\x40\x00\x2f"
advertisement_4 = bytes_to_service_info(
data_string_4, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device.update(advertisement_4)
assert device.packet_id == 47
def test_bthome_wrong_object_id(caplog):
"""Test BTHome parser for a non-existing Object ID xFE."""
data_string = b"\x40\xfe\xca\x09"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_battery_wrong_object_id_humidity(caplog):
"""
Test BTHome parser for battery, wrong object id and humidity reading.
Should only return the battery reading, as humidity is after wrong object id.
"""
data_string = b"\x40\x01\x5d\xfe\x5d\x09\x03\xb7\x18"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_BATTERY: SensorValue(
device_key=KEY_BATTERY, name="Battery", native_value=93
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_with_mac(caplog):
"""Test BTHome parser for pressure reading mac address in payload."""
data_string = b"\x42\xb2\x18\x8d\x38\xc1\xa4\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PRESSURE: SensorDescription(
device_key=KEY_PRESSURE,
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=Units.PRESSURE_MBAR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PRESSURE: SensorValue(
device_key=KEY_PRESSURE, name="Pressure", native_value=1008.83
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_with_mac_encrypted():
"""Test BTHome parser with mac address in payload plus encryption."""
bindkey = "231d39c1d7cc1ab1aee224cd096db932"
data = b"\x43\xa5\x80\x8f\xe6\x48\x54\xf1\x96\xc0\x6f\xfb\x49\x00\x11\x22\x33\xf0\x8e\xcb\xde"
advertisement = bytes_to_service_info(
data,
local_name="TEST DEVICE",
address="54:48:E6:8F:80:A5",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 80A5",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 80A5",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2 (encrypted)",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.06
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=50.55
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_temperature_humidity(caplog):
"""Test BTHome parser for temperature humidity reading without encryption."""
data_string = b"\x40\x02\xca\x09\x03\xbf\x13"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.06
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=50.55
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_packet_id_temperature_humidity_battery(caplog):
"""Test BTHome parser for packet_id, temperature, humidity and battery reading."""
data_string = b"\x40\x00\x09\x01\x5d\x02\x5d\x09\x03\xb7\x18"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=9
),
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=23.97
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=63.27
),
KEY_BATTERY: SensorValue(
device_key=KEY_BATTERY, name="Battery", native_value=93
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_pressure(caplog):
"""Test BTHome parser for pressure reading without encryption."""
data_string = b"\x40\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PRESSURE: SensorDescription(
device_key=KEY_PRESSURE,
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=Units.PRESSURE_MBAR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PRESSURE: SensorValue(
device_key=KEY_PRESSURE, name="Pressure", native_value=1008.83
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_illuminance(caplog):
"""Test BTHome parser for illuminance reading without encryption."""
data_string = b"\x40\x05\x13\x8a\x14"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_ILLUMINANCE: SensorDescription(
device_key=KEY_ILLUMINANCE,
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=Units.LIGHT_LUX,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_ILLUMINANCE: SensorValue(
device_key=KEY_ILLUMINANCE, name="Illuminance", native_value=13460.67
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_mass_kilograms(caplog):
"""Test BTHome parser for mass reading in kilograms without encryption."""
data_string = b"\x40\x06\x5e\x1f"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_MASS: SensorDescription(
device_key=KEY_MASS,
device_class=SensorDeviceClass.MASS,
native_unit_of_measurement=Units.MASS_KILOGRAMS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_MASS: SensorValue(device_key=KEY_MASS, name="Mass", native_value=80.3),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_mass_pounds(caplog):
"""Test BTHome parser for mass reading in pounds without encryption."""
data_string = b"\x40\x07\x3e\x1d"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_MASS: SensorDescription(
device_key=KEY_MASS,
device_class=SensorDeviceClass.MASS,
native_unit_of_measurement=Units.MASS_POUNDS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_MASS: SensorValue(device_key=KEY_MASS, name="Mass", native_value=74.86),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_dew_point(caplog):
"""Test BTHome parser for dew point reading without encryption."""
data_string = b"\x40\x08\xca\x06"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_DEW_POINT: SensorDescription(
device_key=KEY_DEW_POINT,
device_class=SensorDeviceClass.DEW_POINT,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_DEW_POINT: SensorValue(
device_key=KEY_DEW_POINT, name="Dew Point", native_value=17.38
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_count(caplog):
"""Test BTHome parser for counter reading without encryption."""
data_string = b"\x40\x09\x60"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_COUNT: SensorDescription(
device_key=KEY_COUNT,
device_class=SensorDeviceClass.COUNT,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_COUNT: SensorValue(device_key=KEY_COUNT, name="Count", native_value=96),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_energy(caplog):
"""Test BTHome parser for energy reading without encryption."""
data_string = b"\x40\x0a\x13\x8a\x14"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_ENERGY: SensorDescription(
device_key=KEY_ENERGY,
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=Units.ENERGY_KILO_WATT_HOUR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_ENERGY: SensorValue(
device_key=KEY_ENERGY, name="Energy", native_value=1346.067
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_power(caplog):
"""Test BTHome parser for power reading without encryption."""
data_string = b"\x40\x0b\x02\x1b\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_POWER: SensorDescription(
device_key=KEY_POWER,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=Units.POWER_WATT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_POWER: SensorValue(
device_key=KEY_POWER, name="Power", native_value=69.14
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_voltage(caplog):
"""Test BThome parser for voltage reading without encryption."""
data_string = b"\x40\x0c\x02\x0c"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLTAGE: SensorDescription(
device_key=KEY_VOLTAGE,
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLTAGE: SensorValue(
device_key=KEY_VOLTAGE, name="Voltage", native_value=3.074
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_binary_sensor(caplog):
"""Test BTHome parser for binary sensor without device class, without encryption."""
data_string = b"\x40\x0f\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_GENERIC: BinarySensorDescription(
device_key=KEY_BINARY_GENERIC,
device_class=BinarySensorDeviceClass.GENERIC,
),
},
binary_entity_values={
KEY_BINARY_GENERIC: BinarySensorValue(
device_key=KEY_BINARY_GENERIC, name="Generic", native_value=True
),
},
)
def test_bthome_binary_sensor_power(caplog):
"""Test BTHome parser for binary sensor power without encryption."""
data_string = b"\x40\x10\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_POWER: BinarySensorDescription(
device_key=KEY_BINARY_POWER,
device_class=BinarySensorDeviceClass.POWER,
),
},
binary_entity_values={
KEY_BINARY_POWER: BinarySensorValue(
device_key=KEY_BINARY_POWER, name="Power", native_value=True
),
},
)
def test_bthome_binary_sensor_opening(caplog):
"""Test BTHome parser for binary sensor opening without encryption."""
data_string = b"\x40\x11\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_OPENING: BinarySensorDescription(
device_key=KEY_BINARY_OPENING,
device_class=BinarySensorDeviceClass.OPENING,
),
},
binary_entity_values={
KEY_BINARY_OPENING: BinarySensorValue(
device_key=KEY_BINARY_OPENING, name="Opening", native_value=False
),
},
)
def test_bthome_binary_sensor_window(caplog):
"""Test BTHome parser for binary sensor window without encryption."""
data_string = b"\x40\x2d\x01"
advertisement = bytes_to_service_info(
data_string, local_name="SBDW-002C", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="Shelly BLU Door/Window 18B2",
devices={
None: SensorDeviceInfo(
name="Shelly BLU Door/Window 18B2",
manufacturer="Shelly",
model="BLU Door/Window",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_WINDOW: BinarySensorDescription(
device_key=KEY_BINARY_WINDOW,
device_class=BinarySensorDeviceClass.WINDOW,
),
},
binary_entity_values={
KEY_BINARY_WINDOW: BinarySensorValue(
device_key=KEY_BINARY_WINDOW, name="Window", native_value=True
),
},
)
def test_bthome_pm(caplog):
"""Test BTHome parser for PM2.5 and PM10 reading without encryption."""
data_string = b"\x40\x0d\x12\x0c\x0e\x02\x1c"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PM25: SensorDescription(
device_key=KEY_PM25,
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=(
Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
),
),
KEY_PM10: SensorDescription(
device_key=KEY_PM10,
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=(
Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
),
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PM25: SensorValue(device_key=KEY_PM25, name="Pm25", native_value=3090),
KEY_PM10: SensorValue(device_key=KEY_PM10, name="Pm10", native_value=7170),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_co2(caplog):
"""Test BTHome parser for CO2 reading without encryption."""
data_string = b"\x40\x12\xe2\x04"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_CO2: SensorDescription(
device_key=KEY_CO2,
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=Units.CONCENTRATION_PARTS_PER_MILLION,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_CO2: SensorValue(
device_key=KEY_CO2, name="Carbon Dioxide", native_value=1250
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_voc(caplog):
"""Test BTHome parser for VOC reading without encryption."""
data_string = b"\x40\x133\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOC: SensorDescription(
device_key=KEY_VOC,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=Units.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOC: SensorValue(
device_key=KEY_VOC, name="Volatile Organic Compounds", native_value=307
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_moisture(caplog):
"""Test BTHome parser for moisture reading from b-parasite sensor."""
data_string = b"\x40\x14\x02\x0c"
advertisement = bytes_to_service_info(
data_string, local_name="prst", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="b-parasite 18B2",
devices={
None: SensorDeviceInfo(
name="b-parasite 18B2",
manufacturer="b-parasite",
model="Plant sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_MOISTURE: SensorDescription(
device_key=KEY_MOISTURE,
device_class=SensorDeviceClass.MOISTURE,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_MOISTURE: SensorValue(
device_key=KEY_MOISTURE, name="Moisture", native_value=30.74
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_event_button_long_press(caplog):
"""Test BTHome parser for an event of a long press on a button without encryption."""
data_string = b"\x40\x3a\x04"
advertisement = bytes_to_service_info(
data_string, local_name="SBBT-002C", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="Shelly BLU Button1 18B2",
devices={
None: SensorDeviceInfo(
name="Shelly BLU Button1 18B2",
manufacturer="Shelly",
model="BLU Button1",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={
KEY_BUTTON: Event(
device_key=KEY_BUTTON,
name="Button",
event_type="long_press",
event_properties=None,
),
},
)
def test_bthome_event_triple_button_device(caplog):
"""
Test BTHome parser for an event of a triple button device where
the 2nd button is pressed and the 3rd button is triple pressed.
"""
data_string = b"\x40\x3a\x00\x3a\x01\x3a\x03"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={
DeviceKey(key="button_2", device_id=None): Event(
device_key=DeviceKey(key="button_2", device_id=None),
name="Button 2",
event_type="press",
event_properties=None,
),
DeviceKey(key="button_3", device_id=None): Event(
device_key=DeviceKey(key="button_3", device_id=None),
name="Button 3",
event_type="triple_press",
event_properties=None,
),
},
)
def test_bthome_event_button_hold_press(caplog):
"""Test BTHome parser for an event of holding press on a button without encryption."""
data_string = b"\x40\x3a\x80"
advertisement = bytes_to_service_info(
data_string, local_name="SBBT-002C", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="Shelly BLU Button1 18B2",
devices={
None: SensorDeviceInfo(
name="Shelly BLU Button1 18B2",
manufacturer="Shelly",
model="BLU Button1",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={
KEY_BUTTON: Event(
device_key=KEY_BUTTON,
name="Button",
event_type="hold_press",
event_properties=None,
),
},
)
def test_bthome_event_dimmer_rotate_left_3_steps(caplog):
"""Test BTHome parser for an event rotating a dimmer 3 steps left."""
data_string = b"\x40\x3c\x01\x03"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={
KEY_DIMMER: Event(
device_key=KEY_DIMMER,
name="Dimmer",
event_type="rotate_left",
event_properties={"steps": 3},
),
},
)
def test_bthome_rotation(caplog):
"""Test BTHome parser for rotation."""
data_string = b"\x40\x3f\x02\x0c"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_ROTATION: SensorDescription(
device_key=KEY_ROTATION,
device_class=SensorDeviceClass.ROTATION,
native_unit_of_measurement=Units.DEGREE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_ROTATION: SensorValue(
device_key=KEY_ROTATION, name="Rotation", native_value=307.4
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_invalid_button_event(caplog):
"""Test BTHome parser for an invalid button event."""
data_string = b"\x40\x3a\xfe"
advertisement = bytes_to_service_info(
data_string, local_name="SBBT-002C", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="Shelly BLU Button1 18B2",
devices={
None: SensorDeviceInfo(
name="Shelly BLU Button1 18B2",
manufacturer="Shelly",
model="BLU Button1",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={},
)
def test_encrypted_shelly_blu_button_event(caplog):
"""Test BTHome parser for an encrypted shelly blu button event."""
bindkey = "90bffd73cb6b26ef58a7a8eba9232036"
data_string = b"\x45\x0a\xeb\x84\x46\x7f\x85\xba\x01\x00\x00\x31\x88\x78\x8c"
advertisement = bytes_to_service_info(
data_string,
local_name="SBBT-002C",
address="B4:35:22:F5:3D:A4",
)
device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
assert device.supported(advertisement)
assert device.bindkey_verified
def test_bthome_distance_millimeters(caplog):
"""Test BTHome parser for distance in millimeters."""
data_string = b"\x40\x40\x0c\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_DISTANCE: SensorDescription(
device_key=KEY_DISTANCE,
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=Units.LENGTH_MILLIMETERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_DISTANCE: SensorValue(
device_key=KEY_DISTANCE, name="Distance", native_value=12
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_distance_meters(caplog):
"""Test BTHome parser for distance in meters."""
data_string = b"\x40\x41\x4e\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_DISTANCE: SensorDescription(
device_key=KEY_DISTANCE,
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=Units.LENGTH_METERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_DISTANCE: SensorValue(
device_key=KEY_DISTANCE, name="Distance", native_value=7.8
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_duration(caplog):
"""Test BTHome parser for duration in seconds."""
data_string = b"\x40\x42\x4e\x34\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_DURATION: SensorDescription(
device_key=KEY_DURATION,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=Units.TIME_SECONDS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_DURATION: SensorValue(
device_key=KEY_DURATION, name="Duration", native_value=13.39
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_current(caplog):
"""Test BTHome parser for current in VA."""
data_string = b"\x40\x43\x4e\x34"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_CURRENT: SensorDescription(
device_key=KEY_CURRENT,
device_class=SensorDeviceClass.CURRENT,
native_unit_of_measurement=Units.ELECTRIC_CURRENT_AMPERE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_CURRENT: SensorValue(
device_key=KEY_CURRENT, name="Current", native_value=13.39
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_speed(caplog):
"""Test BTHome parser for speed in m/s."""
data_string = b"\x40\x44\x4e\x34"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SPEED: SensorDescription(
device_key=KEY_SPEED,
device_class=SensorDeviceClass.SPEED,
native_unit_of_measurement=Units.SPEED_METERS_PER_SECOND,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SPEED: SensorValue(
device_key=KEY_SPEED, name="Speed", native_value=133.9
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_temperature_2(caplog):
"""Test BTHome parser for temperature with one digit."""
data_string = b"\x40\x45\x11\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=27.3
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_uv_index(caplog):
"""Test BTHome parser for UV index."""
data_string = b"\x40\x46\x32"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_UV_INDEX: SensorDescription(
device_key=KEY_UV_INDEX,
device_class=SensorDeviceClass.UV_INDEX,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_UV_INDEX: SensorValue(
device_key=KEY_UV_INDEX, name="Uv Index", native_value=5.0
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_volume_liters(caplog):
"""Test BTHome parser for Volume in Liters."""
data_string = b"\x40\x47\x87\x56"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLUME: SensorDescription(
device_key=KEY_VOLUME,
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=Units.VOLUME_LITERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLUME: SensorValue(
device_key=KEY_VOLUME, name="Volume", native_value=2215.1
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_volume_milliliters(caplog):
"""Test BTHome parser for Volume in milliliters."""
data_string = b"\x40\x48\xdc\x87"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLUME: SensorDescription(
device_key=KEY_VOLUME,
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=Units.VOLUME_MILLILITERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLUME: SensorValue(
device_key=KEY_VOLUME, name="Volume", native_value=34780
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_volume_flow_rate(caplog):
"""Test BTHome parser for Volume flow rate in m3 per hour."""
data_string = b"\x40\x49\xdc\x87"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLUME_FLOW_RATE: SensorDescription(
device_key=KEY_VOLUME_FLOW_RATE,
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
native_unit_of_measurement=Units.VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLUME_FLOW_RATE: SensorValue(
device_key=KEY_VOLUME_FLOW_RATE,
name="Volume Flow Rate",
native_value=34.780,
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_voltage_2(caplog):
"""Test BThome parser for voltage reading without encryption."""
data_string = b"\x40\x4a\x02\x0c"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLTAGE: SensorDescription(
device_key=KEY_VOLTAGE,
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLTAGE: SensorValue(
device_key=KEY_VOLTAGE, name="Voltage", native_value=307.4
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_gas(caplog):
"""Test BTHome parser for gas reading without encryption."""
data_string = b"\x40\x4b\x13\x8a\x14"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_GAS: SensorDescription(
device_key=KEY_GAS,
device_class=SensorDeviceClass.GAS,
native_unit_of_measurement=Units.VOLUME_CUBIC_METERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_GAS: SensorValue(device_key=KEY_GAS, name="Gas", native_value=1346.067),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_gas_2(caplog):
"""Test BTHome parser for gas reading without encryption."""
data_string = b"\x40\x4c\x41\x01\x8a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_GAS: SensorDescription(
device_key=KEY_GAS,
device_class=SensorDeviceClass.GAS,
native_unit_of_measurement=Units.VOLUME_CUBIC_METERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_GAS: SensorValue(
device_key=KEY_GAS, name="Gas", native_value=25821.505
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_energy_2(caplog):
"""Test BTHome parser for energy reading without encryption."""
data_string = b"\x40\x4d\x12\x13\x8a\x14"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_ENERGY: SensorDescription(
device_key=KEY_ENERGY,
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=Units.ENERGY_KILO_WATT_HOUR,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_ENERGY: SensorValue(
device_key=KEY_ENERGY, name="Energy", native_value=344593.17
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_volume_liters_2(caplog):
"""Test BTHome parser for Volume in Liters."""
data_string = b"\x40\x4e\x87\x56\x2a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLUME: SensorDescription(
device_key=KEY_VOLUME,
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=Units.VOLUME_LITERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLUME: SensorValue(
device_key=KEY_VOLUME, name="Volume", native_value=19551.879
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_volume_water(caplog):
"""Test BTHome parser for water in Liters."""
data_string = b"\x40\x4f\x87\x56\x2a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_WATER: SensorDescription(
device_key=KEY_WATER,
device_class=SensorDeviceClass.WATER,
native_unit_of_measurement=Units.VOLUME_LITERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_WATER: SensorValue(
device_key=KEY_WATER, name="Water", native_value=19551.879
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_timestamp(caplog):
"""Test BTHome parser for Unix timestamp (seconds from 1-1-1970)."""
data_string = b"\x44\x50\x5d\x39\x61\x64"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_TIMESTAMP: SensorDescription(
device_key=KEY_TIMESTAMP,
device_class=SensorDeviceClass.TIMESTAMP,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TIMESTAMP: SensorValue(
device_key=KEY_TIMESTAMP,
name="Timestamp",
native_value=datetime(2023, 5, 14, 19, 41, 17, tzinfo=timezone.utc),
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_acceleration(caplog):
"""Test BTHome parser for acceleration in m/s°."""
data_string = b"\x44\x51\x87\x56"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_ACCELERATION: SensorDescription(
device_key=KEY_ACCELERATION,
device_class=SensorDeviceClass.ACCELERATION,
native_unit_of_measurement=Units.ACCELERATION_METERS_PER_SQUARE_SECOND,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_ACCELERATION: SensorValue(
device_key=KEY_ACCELERATION, name="Acceleration", native_value=22.151
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_gyroscope(caplog):
"""Test BTHome parser for gyroscope in °/s."""
data_string = b"\x44\x52\x87\x56"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_GYROSCOPE: SensorDescription(
device_key=KEY_GYROSCOPE,
device_class=SensorDeviceClass.GYROSCOPE,
native_unit_of_measurement=Units.GYROSCOPE_DEGREES_PER_SECOND,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_GYROSCOPE: SensorValue(
device_key=KEY_GYROSCOPE, name="Gyroscope", native_value=22.151
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_text(caplog):
"""Test BTHome parser for text."""
data_string = b"\x44\x53\x0c\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_TEXT: SensorDescription(
device_key=KEY_TEXT,
device_class=ExtendedSensorDeviceClass.TEXT,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEXT: SensorValue(
device_key=KEY_TEXT, name="Text", native_value="Hello World!"
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_raw(caplog):
"""Test BTHome parser for raw hex data."""
data_string = b"\x44\x54\x0c\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_RAW: SensorDescription(
device_key=KEY_RAW,
device_class=ExtendedSensorDeviceClass.RAW,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_RAW: SensorValue(
device_key=KEY_RAW, name="Raw", native_value="48656c6c6f20576f726c6421"
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_text_invalid(caplog):
"""Test BTHome parser for text sensor with invalid format."""
data_string = b"\x44\x53\x01\x87"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_volume_storage(caplog):
"""Test BTHome parser for volume storage in Liters."""
data_string = b"\x40\x55\x87\x56\x2a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_VOLUME_STORAGE: SensorDescription(
device_key=KEY_VOLUME_STORAGE,
device_class=ExtendedSensorDeviceClass.VOLUME_STORAGE,
native_unit_of_measurement=Units.VOLUME_LITERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_VOLUME_STORAGE: SensorValue(
device_key=KEY_VOLUME_STORAGE,
name="Volume Storage",
native_value=19551.879,
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_conductivity(caplog):
"""Test BTHome parser for conductivity reading from SenseNL sensor."""
data_string = b"\x40\x56\xe8\x03"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_CONDUCTIVITY: SensorDescription(
device_key=KEY_CONDUCTIVITY,
device_class=SensorDeviceClass.CONDUCTIVITY,
native_unit_of_measurement=Units.CONDUCTIVITY,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_CONDUCTIVITY: SensorValue(
device_key=KEY_CONDUCTIVITY, name="Conductivity", native_value=1000
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_temperature_1_byte(caplog):
"""Test BTHome parser for 1 byte temperature reading without encryption."""
data_string = b"\x40\x57\xea"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=-22
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_temperature_1_byte_2(caplog):
"""Test BTHome parser for 1 byte temperature reading without encryption."""
data_string = b"\x40\x58\xea"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=-7.7
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_count_signed(caplog):
"""Test BTHome parser for signed counter reading without encryption."""
data_string = b"\x40\x59\xea"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_COUNT: SensorDescription(
device_key=KEY_COUNT,
device_class=SensorDeviceClass.COUNT,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_COUNT: SensorValue(
device_key=KEY_COUNT, name="Count", native_value=-22
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_count_signed_2_bytes(caplog):
"""Test BTHome parser for signed counter reading without encryption."""
data_string = b"\x40\x5a\xea\xea"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_COUNT: SensorDescription(
device_key=KEY_COUNT,
device_class=SensorDeviceClass.COUNT,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_COUNT: SensorValue(
device_key=KEY_COUNT, name="Count", native_value=-5398
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_count_signed_4_bytes(caplog):
"""Test BTHome parser for signed counter reading without encryption."""
data_string = b"\x40\x5b\xea\x02\x34\xea"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_COUNT: SensorDescription(
device_key=KEY_COUNT,
device_class=SensorDeviceClass.COUNT,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_COUNT: SensorValue(
device_key=KEY_COUNT, name="Count", native_value=-365690134
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_power_signed(caplog):
"""Test BTHome parser for signed power reading without encryption."""
data_string = b"\x40\x5c\x02\xfb\xff\xff"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_POWER: SensorDescription(
device_key=KEY_POWER,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=Units.POWER_WATT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_POWER: SensorValue(
device_key=KEY_POWER, name="Power", native_value=-12.78
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_current_signed(caplog):
"""Test BTHome parser for signed current in VA."""
data_string = b"\x40\x5d\x02\xea"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_CURRENT: SensorDescription(
device_key=KEY_CURRENT,
device_class=SensorDeviceClass.CURRENT,
native_unit_of_measurement=Units.ELECTRIC_CURRENT_AMPERE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_CURRENT: SensorValue(
device_key=KEY_CURRENT, name="Current", native_value=-5.63
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_double_temperature(caplog):
"""Test BTHome parser for double temperature reading without encryption."""
data_string = b"\x40\x02\xca\x09\x02\xcf\x09"
advertisement = bytes_to_service_info(
data_string, local_name="A4:C1:38:8D:18:B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="BTHome sensor 18B2",
devices={
None: SensorDeviceInfo(
name="BTHome sensor 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
DeviceKey(key="temperature_1", device_id=None): SensorDescription(
device_key=DeviceKey(key="temperature_1", device_id=None),
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
DeviceKey(key="temperature_2", device_id=None): SensorDescription(
device_key=DeviceKey(key="temperature_2", device_id=None),
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
DeviceKey(key="temperature_1", device_id=None): SensorValue(
device_key=DeviceKey(key="temperature_1", device_id=None),
name="Temperature 1",
native_value=25.06,
),
DeviceKey(key="temperature_2", device_id=None): SensorValue(
device_key=DeviceKey(key="temperature_2", device_id=None),
name="Temperature 2",
native_value=25.11,
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_triple_temperature_double_humidity_battery(caplog):
"""
Test BTHome parser for triple temperature, double humidity and
single battery reading without encryption.
"""
data_string = (
b"\x40\x02\xca\x09\x02\xcf\x09\x02\xcf\x08\x03\xb7\x18\x03\xb7\x17\x01\x5d"
)
advertisement = bytes_to_service_info(
data_string, local_name="A4:C1:38:8D:18:B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="BTHome sensor 18B2",
devices={
None: SensorDeviceInfo(
name="BTHome sensor 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
DeviceKey(key="temperature_1", device_id=None): SensorDescription(
device_key=DeviceKey(key="temperature_1", device_id=None),
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
DeviceKey(key="temperature_2", device_id=None): SensorDescription(
device_key=DeviceKey(key="temperature_2", device_id=None),
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
DeviceKey(key="temperature_3", device_id=None): SensorDescription(
device_key=DeviceKey(key="temperature_3", device_id=None),
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
DeviceKey(key="humidity_1", device_id=None): SensorDescription(
device_key=DeviceKey(key="humidity_1", device_id=None),
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
DeviceKey(key="humidity_2", device_id=None): SensorDescription(
device_key=DeviceKey(key="humidity_2", device_id=None),
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
DeviceKey(key="temperature_1", device_id=None): SensorValue(
device_key=DeviceKey(key="temperature_1", device_id=None),
name="Temperature 1",
native_value=25.06,
),
DeviceKey(key="temperature_2", device_id=None): SensorValue(
device_key=DeviceKey(key="temperature_2", device_id=None),
name="Temperature 2",
native_value=25.11,
),
DeviceKey(key="temperature_3", device_id=None): SensorValue(
device_key=DeviceKey(key="temperature_3", device_id=None),
name="Temperature 3",
native_value=22.55,
),
DeviceKey(key="humidity_1", device_id=None): SensorValue(
device_key=DeviceKey(key="humidity_1", device_id=None),
name="Humidity 1",
native_value=63.27,
),
DeviceKey(key="humidity_2", device_id=None): SensorValue(
device_key=DeviceKey(key="humidity_2", device_id=None),
name="Humidity 2",
native_value=60.71,
),
KEY_BATTERY: SensorValue(KEY_BATTERY, name="Battery", native_value=93),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_multiple_uuids(caplog):
"""Test BTHome parser for a device that broadcasts multiple uuids."""
advertisement = BluetoothServiceInfoBleak(
name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
rssi=-60,
manufacturer_data={},
service_data={
"0000181a-0000-1000-8000-00805f9b34fb": b"\xc4$\x818\xc1\xa4V\x08\x83\x18\xbf",
"0000fe95-0000-1000-8000-00805f9b34fb": b"0X[\x05\x01\xc4$\x818\xc1\xa4(\x01\x00",
"0000fcd2-0000-1000-8000-00805f9b34fb": b"\x40\x01\x5d\x02\x5d\x09\x03\xb7\x18",
},
service_uuids=[
"0000181a-0000-1000-8000-00805f9b34fb",
"0000fe95-0000-1000-8000-00805f9b34fb",
"0000fcd2-0000-1000-8000-00805f9b34fb",
],
source="",
device=None,
advertisement=None,
connectable=False,
time=ADVERTISEMENT_TIME,
tx_power=None,
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="ATC 18B2",
devices={
None: SensorDeviceInfo(
name="ATC 18B2",
manufacturer="Xiaomi",
model="Temperature/Humidity sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_TEMPERATURE: SensorDescription(
device_key=KEY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
KEY_HUMIDITY: SensorDescription(
device_key=KEY_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_TEMPERATURE: SensorValue(
device_key=KEY_TEMPERATURE, name="Temperature", native_value=23.97
),
KEY_HUMIDITY: SensorValue(
device_key=KEY_HUMIDITY, name="Humidity", native_value=63.27
),
KEY_BATTERY: SensorValue(
device_key=KEY_BATTERY, name="Battery", native_value=93
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_double_voltage_different_object_id(caplog):
"""
Test BTHome parser for double voltage with different object id.
"""
data_string = b"@\x00\x01\x0b\x00\x00\x00J\r\t\x013\x0c\xe9\x0c"
advertisement = bytes_to_service_info(
data_string, local_name="A4:C1:38:8D:18:B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="BTHome sensor 18B2",
devices={
None: SensorDeviceInfo(
name="BTHome sensor 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
DeviceKey(key="power", device_id=None): SensorDescription(
device_key=DeviceKey(key="power", device_id=None),
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=Units.POWER_WATT,
),
DeviceKey(key="voltage_1", device_id=None): SensorDescription(
device_key=DeviceKey(key="voltage_1", device_id=None),
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT,
),
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
DeviceKey(key="voltage_2", device_id=None): SensorDescription(
device_key=DeviceKey(key="voltage_2", device_id=None),
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=1
),
DeviceKey(key="power", device_id=None): SensorValue(
device_key=DeviceKey(key="power", device_id=None),
name="Power",
native_value=0.0,
),
DeviceKey(key="voltage_1", device_id=None): SensorValue(
device_key=DeviceKey(key="voltage_1", device_id=None),
name="Voltage 1",
native_value=231.7,
),
KEY_BATTERY: SensorValue(KEY_BATTERY, name="Battery", native_value=51),
DeviceKey(key="voltage_2", device_id=None): SensorValue(
device_key=DeviceKey(key="voltage_2", device_id=None),
name="Voltage 2",
native_value=3.305,
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_shelly_button(caplog):
"""
Test BTHome parser with a shelly button.
"""
data_string = b"@\x00R\x01d:\x01"
advertisement = bytes_to_service_info(
data_string, local_name="A4:C1:38:8D:18:B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
update = device.update(advertisement)
assert device.supported(advertisement) is True
assert update == SensorUpdate(
title="BTHome sensor 18B2",
devices={
None: SensorDeviceInfo(
name="BTHome sensor 18B2",
model="BTHome sensor",
manufacturer=None,
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
DeviceKey(key="packet_id", device_id=None): SensorDescription(
device_key=DeviceKey(key="packet_id", device_id=None),
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
DeviceKey(key="battery", device_id=None): SensorDescription(
device_key=DeviceKey(key="battery", device_id=None),
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
DeviceKey(key="signal_strength", device_id=None): SensorDescription(
device_key=DeviceKey(key="signal_strength", device_id=None),
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
DeviceKey(key="packet_id", device_id=None): SensorValue(
device_key=DeviceKey(key="packet_id", device_id=None),
name="Packet " "Id",
native_value=82,
),
DeviceKey(key="battery", device_id=None): SensorValue(
device_key=DeviceKey(key="battery", device_id=None),
name="Battery",
native_value=100,
),
DeviceKey(key="signal_strength", device_id=None): SensorValue(
device_key=DeviceKey(key="signal_strength", device_id=None),
name="Signal " "Strength",
native_value=-60,
),
},
binary_entity_descriptions={},
binary_entity_values={},
events={
DeviceKey(key="button", device_id=None): Event(
device_key=DeviceKey(key="button", device_id=None),
name="Button",
event_type="press",
event_properties=None,
)
},
)
def test_bthome_shelly_button_no_press(caplog):
"""Test BTHome parser for button event followed by empty event."""
data_string = b"\x44\x00\x21\x01\x5e\x3a\x01"
advertisement = bytes_to_service_info(
data_string, local_name="SBBT-002C", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="Shelly BLU Button1 18B2",
devices={
None: SensorDeviceInfo(
name="Shelly BLU Button1 18B2",
manufacturer="Shelly",
model="BLU Button1",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=33
),
KEY_BATTERY: SensorValue(KEY_BATTERY, name="Battery", native_value=94),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={
DeviceKey(key="button", device_id=None): Event(
device_key=DeviceKey(key="button", device_id=None),
name="Button",
event_type="press",
event_properties=None,
)
},
)
data_string = b"\x44\x00\x23\x01\x5e\x3a\x00"
advertisement = bytes_to_service_info(
data_string, local_name="SBBT-002C", address="A4:C1:38:8D:18:B2"
)
assert device.update(advertisement) == SensorUpdate(
title="Shelly BLU Button1 18B2",
devices={
None: SensorDeviceInfo(
name="Shelly BLU Button1 18B2",
manufacturer="Shelly",
model="BLU Button1",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
KEY_BATTERY: SensorDescription(
device_key=KEY_BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=35
),
KEY_BATTERY: SensorValue(KEY_BATTERY, name="Battery", native_value=94),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
events={},
)
def test_bthome_test(caplog):
"""Test BTHome parser for acceleration in m/s°."""
data_string = b"@\x00\xa7\x0cx\x0b\x10\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
KEY_PACKET_ID: SensorDescription(
device_key=KEY_PACKET_ID,
device_class=SensorDeviceClass.PACKET_ID,
native_unit_of_measurement=None,
),
KEY_VOLTAGE: SensorDescription(
device_key=KEY_VOLTAGE,
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_PACKET_ID: SensorValue(
device_key=KEY_PACKET_ID, name="Packet Id", native_value=167
),
KEY_VOLTAGE: SensorValue(
device_key=KEY_VOLTAGE, name="Voltage", native_value=2.936
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
binary_entity_descriptions={
KEY_BINARY_POWER: BinarySensorDescription(
device_key=KEY_BINARY_POWER,
device_class=BinarySensorDeviceClass.POWER,
),
},
binary_entity_values={
KEY_BINARY_POWER: BinarySensorValue(
device_key=KEY_BINARY_POWER, name="Power", native_value=False
),
},
)
def test_incorrect_bthome_version(caplog):
"""Test BTHome parser with incorrect BTHome version."""
data_string = b"\x00"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
device.update(advertisement)
assert device.supported(advertisement) is False
assert (
"18B2: Sensor is set to use BTHome version 0, which is not existing. "
"Please modify the version in the first byte of the service data" in caplog.text
)
def test_bthome_not_sending_object_ids_in_numerical_order(caplog):
"""Test BTHome parser for not sending object ids in numerical order."""
data_string = b"\x40\x03\xbf\x13\x02\xca\x09"
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
device.update(advertisement)
assert (
"ATC 18B2: BTHome device is not sending object ids in numerical order "
"(from low to high object id). This can cause issues with your BTHome receiver, "
"payload: 03bf1302ca09" in caplog.text
)
def test_bthome_invalid_object_payload_data_length(caplog):
"""Test BTHome parser for object with invalid payload data length."""
data_string = b"\x40\x02\xca\x09\x03\xbf" # missing 1 byte
advertisement = bytes_to_service_info(
data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
device.update(advertisement)
assert "ATC 18B2: Invalid payload data length, payload: 02ca0903bf" in caplog.text
def test_bthome_direction_precipitation(caplog):
"""Test BTHome parser for Direction(0x5E) and Precipitation(0x5F) without encryption."""
data_string = b"\x40\x5e\x3f\x75\x5f\x11\xd5"
advertisement = bytes_to_service_info(
data_string, local_name="SBWS-90CM", address="A4:C1:38:55:AA:55"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="SBWS-90CM AA55",
devices={
None: SensorDeviceInfo(
name="SBWS-90CM AA55",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
DeviceKey(key="direction", device_id=None): SensorDescription(
device_key=DeviceKey(key="direction", device_id=None),
device_class=ExtendedSensorDeviceClass.DIRECTION,
native_unit_of_measurement=Units.DEGREE,
),
DeviceKey(key="precipitation", device_id=None): SensorDescription(
device_key=DeviceKey(key="precipitation", device_id=None),
device_class=ExtendedSensorDeviceClass.PRECIPITATION,
native_unit_of_measurement=Units.LENGTH_MILLIMETERS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
DeviceKey(key="direction", device_id=None): SensorValue(
device_key=DeviceKey(key="direction", device_id=None),
name="Direction",
native_value=300.15,
),
DeviceKey(key="precipitation", device_id=None): SensorValue(
device_key=DeviceKey(key="precipitation", device_id=None),
name="Precipitation",
native_value=5454.5,
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
def test_bthome_channel(caplog):
"""Test BTHome parser for Channel(0x60) without encryption."""
data_string = b"\x40\x60\x01"
advertisement = bytes_to_service_info(
data_string, local_name="SBRC-005B", address="28:DB:A7:B5:D4:03"
)
device = BTHomeBluetoothDeviceData()
assert device.update(advertisement) == SensorUpdate(
title="SBRC-005B D403",
devices={
None: SensorDeviceInfo(
name="SBRC-005B D403",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v2",
hw_version=None,
)
},
entity_descriptions={
DeviceKey(key="channel", device_id=None): SensorDescription(
device_key=DeviceKey(key="channel", device_id=None),
device_class=ExtendedSensorDeviceClass.CHANNEL,
native_unit_of_measurement=None,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
DeviceKey(key="channel", device_id=None): SensorValue(
device_key=DeviceKey(key="channel", device_id=None),
name="Channel",
native_value=1,
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
bthome-ble-3.13.0/tests/test_v1_encryption.py 0000664 0000000 0000000 00000001446 15015405444 0021176 0 ustar 00root root 0000000 0000000 """Tests for the parser of BLE advertisements in BTHome V1 format."""
import binascii
from bthome_ble.bthome_v1_encryption import decrypt_aes_ccm, encrypt_payload
def test_encryption_example():
"""Test BTHome V1 encryption example."""
data = bytes(bytearray.fromhex("2302CA090303BF13")) # BTHome data (not encrypted)
count_id = bytes(bytearray.fromhex("00112233")) # count id (change every message)
mac = binascii.unhexlify("5448E68F80A5") # MAC
uuid16 = b"\x1e\x18"
bindkey = binascii.unhexlify("231d39c1d7cc1ab1aee224cd096db932")
payload = encrypt_payload(
data=data, mac=mac, uuid16=uuid16, count_id=count_id, key=bindkey
)
assert decrypt_aes_ccm(key=bindkey, mac=mac, data=payload) == {
"humidity": 50.55,
"temperature": 25.06,
}
bthome-ble-3.13.0/tests/test_v2_encryption.py 0000664 0000000 0000000 00000002017 15015405444 0021172 0 ustar 00root root 0000000 0000000 """Tests for the parser of BLE advertisements in BTHome V2 format."""
import binascii
from bthome_ble.bthome_v2_encryption import decrypt_aes_ccm, encrypt_payload
def test_encryption_example():
"""Test BTHome V2 encryption example."""
data = bytes(bytearray.fromhex("02CA0903BF13")) # BTHome data (not encrypted)
count_id = bytes(bytearray.fromhex("00112233")) # count id (change every message)
mac = binascii.unhexlify("5448E68F80A5") # MAC
uuid16 = b"\xd2\xfc"
sw_version = b"\x41"
bindkey = binascii.unhexlify("231d39c1d7cc1ab1aee224cd096db932")
encrypted_payload = encrypt_payload(
data=data,
mac=mac,
uuid16=uuid16,
sw_version=sw_version,
count_id=count_id,
key=bindkey,
)
assert (
encrypted_payload
== b"\xd2\xfc\x41\xa4\x72\x66\xc9\x5f\x73\x00\x11\x22\x33\x78\x23\x72\x14"
)
assert decrypt_aes_ccm(key=bindkey, mac=mac, data=encrypted_payload) == {
"humidity": 50.55,
"temperature": 25.06,
}