bleak-smlight 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2026 Bluetooth Devices Authors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: bleak-smlight
3
+ Version: 1.0.0
4
+ Summary: Bleak backend for SMLIGHT SLZB Bluetooth proxies
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: Bluetooth Devices Authors
8
+ Author-email: bluetooth@koston.org
9
+ Requires-Python: >=3.12,<4
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Dist: bluetooth-data-tools (>=1.18.0)
21
+ Requires-Dist: habluetooth (>=6.4.0)
22
+ Requires-Dist: pysmlight (>=0.4.0)
23
+ Project-URL: Bug Tracker, https://github.com/bluetooth-devices/bleak-smlight/issues
24
+ Project-URL: Changelog, https://github.com/bluetooth-devices/bleak-smlight/blob/main/CHANGELOG.md
25
+ Project-URL: Documentation, https://bleak-smlight.readthedocs.io
26
+ Project-URL: Repository, https://github.com/bluetooth-devices/bleak-smlight
27
+ Description-Content-Type: text/markdown
28
+
29
+ # bleak-smlight
30
+
31
+ <p align="center">
32
+ <a href="https://github.com/bluetooth-devices/bleak-smlight/actions/workflows/ci.yml?query=branch%3Amain">
33
+ <img src="https://img.shields.io/github/actions/workflow/status/bluetooth-devices/bleak-smlight/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >
34
+ </a>
35
+ <a href="https://bleak-smlight.readthedocs.io">
36
+ <img src="https://img.shields.io/readthedocs/bleak-smlight.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
37
+ </a>
38
+ <a href="https://codecov.io/gh/bluetooth-devices/bleak-smlight">
39
+ <img src="https://img.shields.io/codecov/c/github/bluetooth-devices/bleak-smlight.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
40
+ </a>
41
+ <a href="https://codspeed.io/Bluetooth-Devices/bleak-smlight"><img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed Badge"/></a>
42
+ </p>
43
+ <p align="center">
44
+ <a href="https://python-poetry.org/">
45
+ <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">
46
+ </a>
47
+ <a href="https://github.com/astral-sh/ruff">
48
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square" alt="Ruff">
49
+ </a>
50
+ <a href="https://github.com/pre-commit/pre-commit">
51
+ <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
52
+ </a>
53
+ </p>
54
+ <p align="center">
55
+ <a href="https://pypi.org/project/bleak-smlight/">
56
+ <img src="https://img.shields.io/pypi/v/bleak-smlight.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
57
+ </a>
58
+ <img src="https://img.shields.io/pypi/pyversions/bleak-smlight.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
59
+ <img src="https://img.shields.io/pypi/l/bleak-smlight.svg?style=flat-square" alt="License">
60
+ </p>
61
+
62
+ ---
63
+
64
+ **Documentation**: <a href="https://bleak-smlight.readthedocs.io" target="_blank">https://bleak-smlight.readthedocs.io </a>
65
+
66
+ **Source Code**: <a href="https://github.com/bluetooth-devices/bleak-smlight" target="_blank">https://github.com/bluetooth-devices/bleak-smlight </a>
67
+
68
+ ---
69
+
70
+ A [Bleak](https://github.com/hbldh/bleak) backend that receives Bluetooth Low
71
+ Energy advertisements from a SMLIGHT SLZB BLE proxy. The proxy is scan only, so
72
+ this library registers a non connectable scanner with
73
+ [habluetooth](https://github.com/Bluetooth-Devices/habluetooth); it does not
74
+ support GATT connections. The UDP proxy protocol lives in
75
+ [pysmlight](https://github.com/smlight-tech/pysmlight), this library is the host
76
+ side glue that feeds advertisements into Bleak. See the [architecture
77
+ docs](https://bleak-smlight.readthedocs.io/en/latest/architecture.html) for how
78
+ the pieces fit together.
79
+
80
+ ## Usage
81
+
82
+ ```python
83
+ import asyncio
84
+
85
+ import habluetooth
86
+
87
+ from bleak_smlight import SMLIGHTConnectionManager, SMLIGHTDeviceConfig
88
+
89
+ DEVICES: list[SMLIGHTDeviceConfig] = [
90
+ {"source": "AA:BB:CC:DD:EE:FF", "name": "slzb-proxy", "host": "10.0.0.5"},
91
+ ]
92
+
93
+
94
+ async def main() -> None:
95
+ await habluetooth.BluetoothManager().async_setup()
96
+ managers = [SMLIGHTConnectionManager(d) for d in DEVICES]
97
+ await asyncio.gather(*(m.start() for m in managers))
98
+ try:
99
+ await asyncio.Event().wait() # advertisements now flow into Bleak
100
+ finally:
101
+ await asyncio.gather(*(m.stop() for m in managers))
102
+
103
+
104
+ asyncio.run(main())
105
+ ```
106
+
107
+ ## Installation
108
+
109
+ Install this via pip (or your favourite package manager):
110
+
111
+ `pip install bleak-smlight`
112
+
113
+ ## Contributors ✨
114
+
115
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
116
+
117
+ <!-- prettier-ignore-start -->
118
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
119
+ <!-- markdownlint-disable -->
120
+ <!-- markdownlint-enable -->
121
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
122
+ <!-- prettier-ignore-end -->
123
+
124
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
125
+
126
+ ## Credits
127
+
128
+ This package was created with
129
+ [Copier](https://copier.readthedocs.io/) and the
130
+ [browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template)
131
+ project template.
132
+
@@ -0,0 +1,103 @@
1
+ # bleak-smlight
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/bluetooth-devices/bleak-smlight/actions/workflows/ci.yml?query=branch%3Amain">
5
+ <img src="https://img.shields.io/github/actions/workflow/status/bluetooth-devices/bleak-smlight/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >
6
+ </a>
7
+ <a href="https://bleak-smlight.readthedocs.io">
8
+ <img src="https://img.shields.io/readthedocs/bleak-smlight.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
9
+ </a>
10
+ <a href="https://codecov.io/gh/bluetooth-devices/bleak-smlight">
11
+ <img src="https://img.shields.io/codecov/c/github/bluetooth-devices/bleak-smlight.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
12
+ </a>
13
+ <a href="https://codspeed.io/Bluetooth-Devices/bleak-smlight"><img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed Badge"/></a>
14
+ </p>
15
+ <p align="center">
16
+ <a href="https://python-poetry.org/">
17
+ <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">
18
+ </a>
19
+ <a href="https://github.com/astral-sh/ruff">
20
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square" alt="Ruff">
21
+ </a>
22
+ <a href="https://github.com/pre-commit/pre-commit">
23
+ <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
24
+ </a>
25
+ </p>
26
+ <p align="center">
27
+ <a href="https://pypi.org/project/bleak-smlight/">
28
+ <img src="https://img.shields.io/pypi/v/bleak-smlight.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
29
+ </a>
30
+ <img src="https://img.shields.io/pypi/pyversions/bleak-smlight.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
31
+ <img src="https://img.shields.io/pypi/l/bleak-smlight.svg?style=flat-square" alt="License">
32
+ </p>
33
+
34
+ ---
35
+
36
+ **Documentation**: <a href="https://bleak-smlight.readthedocs.io" target="_blank">https://bleak-smlight.readthedocs.io </a>
37
+
38
+ **Source Code**: <a href="https://github.com/bluetooth-devices/bleak-smlight" target="_blank">https://github.com/bluetooth-devices/bleak-smlight </a>
39
+
40
+ ---
41
+
42
+ A [Bleak](https://github.com/hbldh/bleak) backend that receives Bluetooth Low
43
+ Energy advertisements from a SMLIGHT SLZB BLE proxy. The proxy is scan only, so
44
+ this library registers a non connectable scanner with
45
+ [habluetooth](https://github.com/Bluetooth-Devices/habluetooth); it does not
46
+ support GATT connections. The UDP proxy protocol lives in
47
+ [pysmlight](https://github.com/smlight-tech/pysmlight), this library is the host
48
+ side glue that feeds advertisements into Bleak. See the [architecture
49
+ docs](https://bleak-smlight.readthedocs.io/en/latest/architecture.html) for how
50
+ the pieces fit together.
51
+
52
+ ## Usage
53
+
54
+ ```python
55
+ import asyncio
56
+
57
+ import habluetooth
58
+
59
+ from bleak_smlight import SMLIGHTConnectionManager, SMLIGHTDeviceConfig
60
+
61
+ DEVICES: list[SMLIGHTDeviceConfig] = [
62
+ {"source": "AA:BB:CC:DD:EE:FF", "name": "slzb-proxy", "host": "10.0.0.5"},
63
+ ]
64
+
65
+
66
+ async def main() -> None:
67
+ await habluetooth.BluetoothManager().async_setup()
68
+ managers = [SMLIGHTConnectionManager(d) for d in DEVICES]
69
+ await asyncio.gather(*(m.start() for m in managers))
70
+ try:
71
+ await asyncio.Event().wait() # advertisements now flow into Bleak
72
+ finally:
73
+ await asyncio.gather(*(m.stop() for m in managers))
74
+
75
+
76
+ asyncio.run(main())
77
+ ```
78
+
79
+ ## Installation
80
+
81
+ Install this via pip (or your favourite package manager):
82
+
83
+ `pip install bleak-smlight`
84
+
85
+ ## Contributors ✨
86
+
87
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
88
+
89
+ <!-- prettier-ignore-start -->
90
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
91
+ <!-- markdownlint-disable -->
92
+ <!-- markdownlint-enable -->
93
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
94
+ <!-- prettier-ignore-end -->
95
+
96
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
97
+
98
+ ## Credits
99
+
100
+ This package was created with
101
+ [Copier](https://copier.readthedocs.io/) and the
102
+ [browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template)
103
+ project template.
@@ -0,0 +1,64 @@
1
+ """Build optional cython modules."""
2
+
3
+ import logging
4
+ import os
5
+ from typing import Any
6
+
7
+ from distutils.command.build_ext import build_ext
8
+
9
+ _LOGGER = logging.getLogger(__name__)
10
+
11
+ try:
12
+ from setuptools import Extension
13
+ except ImportError:
14
+ from distutils.core import Extension
15
+
16
+
17
+ TO_CYTHONIZE = ["src/bleak_smlight/backend/scanner.py"]
18
+
19
+ EXTENSIONS = [
20
+ Extension(
21
+ ext.removeprefix("src/").removesuffix(".py").replace("/", "."),
22
+ [ext],
23
+ language="c",
24
+ extra_compile_args=["-O3", "-g0"],
25
+ )
26
+ for ext in TO_CYTHONIZE
27
+ ]
28
+
29
+
30
+ class BuildExt(build_ext):
31
+ """Build extension."""
32
+
33
+ def build_extensions(self) -> None:
34
+ """Build extensions."""
35
+ if getattr(self, "parallel", None) is None:
36
+ self.parallel = os.cpu_count() or 1
37
+ try:
38
+ super().build_extensions()
39
+ except Exception: # nosec
40
+ _LOGGER.debug("Failed to build extensions", exc_info=True)
41
+
42
+
43
+ def build(setup_kwargs: Any) -> None:
44
+ """Build optional cython modules."""
45
+ if os.environ.get("SKIP_CYTHON"):
46
+ return
47
+ try:
48
+ from Cython.Build import cythonize
49
+
50
+ setup_kwargs.update(
51
+ {
52
+ "ext_modules": cythonize(
53
+ EXTENSIONS,
54
+ compiler_directives={"language_level": "3"}, # Python 3
55
+ ),
56
+ "cmdclass": {"build_ext": BuildExt},
57
+ }
58
+ )
59
+ setup_kwargs["exclude_package_data"] = {
60
+ pkg: ["*.c"] for pkg in setup_kwargs["packages"]
61
+ }
62
+ except Exception:
63
+ if os.environ.get("REQUIRE_CYTHON"):
64
+ raise
@@ -0,0 +1,177 @@
1
+ [project]
2
+ name = "bleak-smlight"
3
+ version = "1.0.0"
4
+ description = "Bleak backend for SMLIGHT SLZB Bluetooth proxies"
5
+ authors = [
6
+ { name = "Bluetooth Devices Authors", email = "bluetooth@koston.org" },
7
+ { name = "Tim Lunn", email = "tl@smlight.tech" },
8
+ ]
9
+ license.text = "MIT"
10
+ readme = "README.md"
11
+ requires-python = ">=3.12,<4"
12
+
13
+ [project.urls]
14
+ "Documentation" = "https://bleak-smlight.readthedocs.io"
15
+ "Repository" = "https://github.com/bluetooth-devices/bleak-smlight"
16
+ "Bug Tracker" = "https://github.com/bluetooth-devices/bleak-smlight/issues"
17
+ "Changelog" = "https://github.com/bluetooth-devices/bleak-smlight/blob/main/CHANGELOG.md"
18
+
19
+ [tool.poetry]
20
+ classifiers = [
21
+ "Development Status :: 2 - Pre-Alpha",
22
+ "Intended Audience :: Developers",
23
+ "Natural Language :: English",
24
+ "Operating System :: OS Independent",
25
+ "Topic :: Software Development :: Libraries",
26
+ ]
27
+ packages = [
28
+ { include = "bleak_smlight", from = "src" },
29
+ ]
30
+ # Make sure we don't package temporary C files generated by the build process
31
+ exclude = [ "**/*.c" ]
32
+
33
+ [tool.poetry.build]
34
+ generate-setup-file = true
35
+ script = "build_ext.py"
36
+
37
+ [tool.poetry.dependencies]
38
+ python = ">=3.12,<4"
39
+ bluetooth-data-tools = ">=1.18.0"
40
+ habluetooth = ">=6.4.0"
41
+ pysmlight = ">=0.4.0"
42
+
43
+ [tool.poetry.group.dev.dependencies]
44
+ pytest = ">=9.0.3,<10"
45
+ pytest-cov = ">=3,<8"
46
+ pytest-codspeed = "^5.0.2"
47
+ pytest-asyncio = "^1.3.0"
48
+ bleak-retry-connector = ">=3.8.0"
49
+ bluetooth-adapters = ">=0.21.0"
50
+
51
+ [tool.poetry.group.docs]
52
+ optional = true
53
+
54
+ [tool.poetry.group.docs.dependencies]
55
+ myst-parser = ">=5.1.0"
56
+ sphinx = ">=4.0"
57
+ furo = ">=2023.5.20"
58
+ sphinx-autobuild = ">=2021.3.14"
59
+
60
+ [tool.semantic_release]
61
+ version_toml = ["pyproject.toml:project.version"]
62
+ version_variables = [
63
+ "src/bleak_smlight/__init__.py:__version__",
64
+ "docs/conf.py:release",
65
+ ]
66
+ build_command = "pip install poetry && poetry build"
67
+
68
+ [tool.semantic_release.changelog]
69
+ exclude_commit_patterns = [
70
+ "chore*",
71
+ "ci*",
72
+ ]
73
+
74
+ [tool.semantic_release.changelog.environment]
75
+ keep_trailing_newline = true
76
+
77
+ [tool.semantic_release.branches.main]
78
+ match = "main"
79
+
80
+ [tool.semantic_release.branches.noop]
81
+ match = "(?!main$)"
82
+ prerelease = true
83
+
84
+ [tool.pytest.ini_options]
85
+ addopts = "-v -Wdefault --cov=bleak_smlight --cov-report=term-missing:skip-covered"
86
+ pythonpath = ["src"]
87
+
88
+ [tool.coverage.run]
89
+ branch = true
90
+
91
+ [tool.coverage.report]
92
+ exclude_lines = [
93
+ "pragma: no cover",
94
+ "@overload",
95
+ "if TYPE_CHECKING",
96
+ "raise NotImplementedError",
97
+ 'if __name__ == "__main__":',
98
+ ]
99
+
100
+ [tool.ruff]
101
+ target-version = "py312"
102
+ line-length = 88
103
+
104
+ [tool.ruff.lint]
105
+ ignore = [
106
+ "D203", # 1 blank line required before class docstring
107
+ "D212", # Multi-line docstring summary should start at the first line
108
+ "D100", # Missing docstring in public module
109
+ "D104", # Missing docstring in public package
110
+ "D107", # Missing docstring in `__init__`
111
+ "D401", # First line of docstring should be in imperative mood
112
+ ]
113
+ select = [
114
+ "B", # flake8-bugbear
115
+ "D", # flake8-docstrings
116
+ "C4", # flake8-comprehensions
117
+ "S", # flake8-bandit
118
+ "F", # pyflake
119
+ "E", # pycodestyle
120
+ "W", # pycodestyle
121
+ "UP", # pyupgrade
122
+ "I", # isort
123
+ "RUF", # ruff specific
124
+ "PLC0415", # pylint: import should be at top-level of file
125
+ "TC", # flake8-type-checking
126
+ "PERF", # Perflint: performance anti-patterns
127
+ "PIE", # flake8-pie: misc lint
128
+ "LOG", # flake8-logging
129
+ "G", # flake8-logging-format
130
+ "RET", # flake8-return
131
+ "FURB", # refurb: modernize stdlib usage
132
+ ]
133
+
134
+ [tool.ruff.lint.per-file-ignores]
135
+ "tests/**/*" = [
136
+ "D100",
137
+ "D101",
138
+ "D102",
139
+ "D103",
140
+ "D104",
141
+ "S101",
142
+ "TC",
143
+ ]
144
+ "setup.py" = ["D100"]
145
+ "conftest.py" = ["D100"]
146
+ "docs/conf.py" = ["D100"]
147
+ "build_ext.py" = ["PLC0415"]
148
+
149
+ [tool.ruff.lint.isort]
150
+ known-first-party = ["bleak_smlight", "tests"]
151
+
152
+ [tool.mypy]
153
+ check_untyped_defs = true
154
+ disallow_any_generics = true
155
+ disallow_incomplete_defs = true
156
+ disallow_untyped_defs = true
157
+ mypy_path = "src/"
158
+ no_implicit_optional = true
159
+ show_error_codes = true
160
+ warn_unreachable = true
161
+ warn_unused_ignores = true
162
+ exclude = [
163
+ 'docs/.*',
164
+ 'setup.py',
165
+ ]
166
+
167
+ [[tool.mypy.overrides]]
168
+ module = "tests.*"
169
+ allow_untyped_defs = true
170
+
171
+ [[tool.mypy.overrides]]
172
+ module = "docs.*"
173
+ ignore_errors = true
174
+
175
+ [build-system]
176
+ requires = ['setuptools>=75.8.2', 'Cython>=3', "poetry-core>=2.0.0"]
177
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ from setuptools import setup
3
+
4
+ package_dir = \
5
+ {'': 'src'}
6
+
7
+ packages = \
8
+ ['bleak_smlight', 'bleak_smlight.backend']
9
+
10
+ package_data = \
11
+ {'': ['*']}
12
+
13
+ install_requires = \
14
+ ['bluetooth-data-tools>=1.18.0', 'habluetooth>=6.4.0', 'pysmlight>=0.4.0']
15
+
16
+ setup_kwargs = {
17
+ 'name': 'bleak-smlight',
18
+ 'version': '1.0.0',
19
+ 'description': 'Bleak backend for SMLIGHT SLZB Bluetooth proxies',
20
+ 'long_description': '# bleak-smlight\n\n<p align="center">\n <a href="https://github.com/bluetooth-devices/bleak-smlight/actions/workflows/ci.yml?query=branch%3Amain">\n <img src="https://img.shields.io/github/actions/workflow/status/bluetooth-devices/bleak-smlight/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >\n </a>\n <a href="https://bleak-smlight.readthedocs.io">\n <img src="https://img.shields.io/readthedocs/bleak-smlight.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">\n </a>\n <a href="https://codecov.io/gh/bluetooth-devices/bleak-smlight">\n <img src="https://img.shields.io/codecov/c/github/bluetooth-devices/bleak-smlight.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">\n </a>\n <a href="https://codspeed.io/Bluetooth-Devices/bleak-smlight"><img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed Badge"/></a>\n</p>\n<p align="center">\n <a href="https://python-poetry.org/">\n <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">\n </a>\n <a href="https://github.com/astral-sh/ruff">\n <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square" alt="Ruff">\n </a>\n <a href="https://github.com/pre-commit/pre-commit">\n <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">\n </a>\n</p>\n<p align="center">\n <a href="https://pypi.org/project/bleak-smlight/">\n <img src="https://img.shields.io/pypi/v/bleak-smlight.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">\n </a>\n <img src="https://img.shields.io/pypi/pyversions/bleak-smlight.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">\n <img src="https://img.shields.io/pypi/l/bleak-smlight.svg?style=flat-square" alt="License">\n</p>\n\n---\n\n**Documentation**: <a href="https://bleak-smlight.readthedocs.io" target="_blank">https://bleak-smlight.readthedocs.io </a>\n\n**Source Code**: <a href="https://github.com/bluetooth-devices/bleak-smlight" target="_blank">https://github.com/bluetooth-devices/bleak-smlight </a>\n\n---\n\nA [Bleak](https://github.com/hbldh/bleak) backend that receives Bluetooth Low\nEnergy advertisements from a SMLIGHT SLZB BLE proxy. The proxy is scan only, so\nthis library registers a non connectable scanner with\n[habluetooth](https://github.com/Bluetooth-Devices/habluetooth); it does not\nsupport GATT connections. The UDP proxy protocol lives in\n[pysmlight](https://github.com/smlight-tech/pysmlight), this library is the host\nside glue that feeds advertisements into Bleak. See the [architecture\ndocs](https://bleak-smlight.readthedocs.io/en/latest/architecture.html) for how\nthe pieces fit together.\n\n## Usage\n\n```python\nimport asyncio\n\nimport habluetooth\n\nfrom bleak_smlight import SMLIGHTConnectionManager, SMLIGHTDeviceConfig\n\nDEVICES: list[SMLIGHTDeviceConfig] = [\n {"source": "AA:BB:CC:DD:EE:FF", "name": "slzb-proxy", "host": "10.0.0.5"},\n]\n\n\nasync def main() -> None:\n await habluetooth.BluetoothManager().async_setup()\n managers = [SMLIGHTConnectionManager(d) for d in DEVICES]\n await asyncio.gather(*(m.start() for m in managers))\n try:\n await asyncio.Event().wait() # advertisements now flow into Bleak\n finally:\n await asyncio.gather(*(m.stop() for m in managers))\n\n\nasyncio.run(main())\n```\n\n## Installation\n\nInstall this via pip (or your favourite package manager):\n\n`pip install bleak-smlight`\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- prettier-ignore-start -->\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- markdownlint-disable -->\n<!-- markdownlint-enable -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n<!-- prettier-ignore-end -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n\n## Credits\n\nThis package was created with\n[Copier](https://copier.readthedocs.io/) and the\n[browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template)\nproject template.\n',
21
+ 'author': 'Bluetooth Devices Authors',
22
+ 'author_email': 'bluetooth@koston.org',
23
+ 'maintainer': 'None',
24
+ 'maintainer_email': 'None',
25
+ 'url': 'https://github.com/bluetooth-devices/bleak-smlight',
26
+ 'package_dir': package_dir,
27
+ 'packages': packages,
28
+ 'package_data': package_data,
29
+ 'install_requires': install_requires,
30
+ 'python_requires': '>=3.12,<4',
31
+ }
32
+ from build_ext import *
33
+ build(setup_kwargs)
34
+
35
+ setup(**setup_kwargs)
@@ -0,0 +1,15 @@
1
+ """Host-side Bleak backend for SMLIGHT SLZB BLE proxies."""
2
+
3
+ from .connect import SLZB_BLE_SERVER_PORT, SMLIGHTClientData, connect_scanner
4
+ from .connection_manager import SMLIGHTConnectionManager, SMLIGHTDeviceConfig
5
+
6
+ __version__ = "1.0.0"
7
+
8
+ __all__ = [
9
+ "SLZB_BLE_SERVER_PORT",
10
+ "SMLIGHTClientData",
11
+ "SMLIGHTConnectionManager",
12
+ "SMLIGHTDeviceConfig",
13
+ "__version__",
14
+ "connect_scanner",
15
+ ]
@@ -0,0 +1,3 @@
1
+
2
+
3
+ cdef object MONOTONIC_TIME
@@ -0,0 +1,40 @@
1
+ """Bluetooth scanner for SMLIGHT SLZB BLE proxy devices."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from bluetooth_data_tools import monotonic_time_coarse as MONOTONIC_TIME
6
+ from habluetooth.base_scanner import BaseHaRemoteScanner
7
+
8
+
9
+ class SMLIGHTScanner(BaseHaRemoteScanner):
10
+ """
11
+ Remote scanner fed by a SMLIGHT SLZB BLE proxy.
12
+
13
+ The SLZB firmware only relays raw BLE advertisements over UDP; it has
14
+ no GATT/active-connection support, so the scanner is always registered
15
+ as non-connectable and has no client to talk back to.
16
+ """
17
+
18
+ def _handle_raw_advertisement(
19
+ self,
20
+ device_mac: str,
21
+ rssi: int,
22
+ address_type: int,
23
+ raw_data: bytes,
24
+ ) -> None:
25
+ """
26
+ Forward one proxied advertisement to habluetooth.
27
+
28
+ This is the callback handed to ``pysmlight.BleProxyClient``. The
29
+ proxy client already decodes the MAC into the canonical
30
+ ``AA:BB:CC:DD:EE:FF`` string and converts the RSSI to a signed
31
+ ``int``, so the values can be passed straight through without any
32
+ further conversion.
33
+ """
34
+ self._async_on_raw_advertisement(
35
+ device_mac,
36
+ rssi,
37
+ raw_data,
38
+ {"address_type": address_type},
39
+ MONOTONIC_TIME(),
40
+ )
@@ -0,0 +1,57 @@
1
+ """Bluetooth proxy support for SMLIGHT SLZB devices."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from dataclasses import dataclass
7
+
8
+ from pysmlight import BleProxyClient
9
+
10
+ from .backend.scanner import SMLIGHTScanner
11
+
12
+ _LOGGER = logging.getLogger(__name__)
13
+
14
+ # Default UDP port of the SLZB BLE proxy server (SLZB-OS).
15
+ SLZB_BLE_SERVER_PORT = 5050
16
+
17
+
18
+ @dataclass(slots=True)
19
+ class SMLIGHTClientData:
20
+ """The scanner and its BLE proxy client for one SLZB device."""
21
+
22
+ scanner: SMLIGHTScanner
23
+ client: BleProxyClient
24
+
25
+
26
+ def connect_scanner(
27
+ source: str,
28
+ name: str,
29
+ host: str,
30
+ port: int = SLZB_BLE_SERVER_PORT,
31
+ ) -> SMLIGHTClientData:
32
+ """
33
+ Build a scanner and BLE proxy client for an SLZB device.
34
+
35
+ ``source`` is the stable unique identifier for the proxy (typically
36
+ its MAC address); ``name`` is the human-friendly adapter name;
37
+ ``host`` is the IP/hostname the UDP proxy server listens on.
38
+
39
+ The caller is responsible for:
40
+
41
+ 1. Calling ``data.scanner.async_setup()``.
42
+ 2. Registering ``data.scanner`` with the habluetooth manager (and
43
+ unregistering it on teardown).
44
+ 3. Calling ``await data.client.start()`` to begin receiving
45
+ advertisements, and ``data.client.stop()`` on teardown.
46
+
47
+ :class:`SMLIGHTConnectionManager` wires all of this up for the common
48
+ standalone case.
49
+ """
50
+ _LOGGER.debug("%s [%s]: Connecting scanner to %s:%s", name, source, host, port)
51
+ scanner = SMLIGHTScanner(source, name, None, False)
52
+ client = BleProxyClient(
53
+ esp32_ip=host,
54
+ callback=scanner._handle_raw_advertisement,
55
+ esp32_port=port,
56
+ )
57
+ return SMLIGHTClientData(scanner=scanner, client=client)
@@ -0,0 +1,84 @@
1
+ """Standalone connection manager for a SMLIGHT SLZB BLE proxy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING, NotRequired, TypedDict
7
+
8
+ from habluetooth import get_manager
9
+
10
+ from .connect import SLZB_BLE_SERVER_PORT, connect_scanner
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Callable
14
+
15
+ from pysmlight import BleProxyClient
16
+
17
+ _LOGGER = logging.getLogger(__name__)
18
+
19
+
20
+ class SMLIGHTDeviceConfig(TypedDict):
21
+ """Configuration for one SMLIGHT SLZB BLE proxy device."""
22
+
23
+ source: str
24
+ name: str
25
+ host: str
26
+ port: NotRequired[int]
27
+
28
+
29
+ class SMLIGHTConnectionManager:
30
+ """
31
+ Manage a scanner for a SMLIGHT SLZB BLE proxy.
32
+
33
+ Construction is side-effect-free and does not require a running event
34
+ loop; all asyncio work happens in :meth:`start`. The underlying
35
+ ``BleProxyClient`` owns its own connect/retry loop, so the manager only
36
+ has to register the scanner with the habluetooth manager and start the
37
+ proxy client.
38
+ """
39
+
40
+ def __init__(self, config: SMLIGHTDeviceConfig) -> None:
41
+ """Initialize the connection manager from ``config``."""
42
+ self._source = config["source"]
43
+ self._name = config["name"]
44
+ self._host = config["host"]
45
+ self._port = config.get("port", SLZB_BLE_SERVER_PORT)
46
+ self._client: BleProxyClient | None = None
47
+ self._unregister_scanner: Callable[[], None] | None = None
48
+ self._unsetup_scanner: Callable[[], None] | None = None
49
+
50
+ async def start(self) -> None:
51
+ """
52
+ Build the scanner, register it, and start the BLE proxy client.
53
+
54
+ Call once per manager instance; a second call raises
55
+ ``RuntimeError`` rather than leaking the prior proxy client and its
56
+ background reconnect task.
57
+
58
+ Raises:
59
+ RuntimeError: if :meth:`start` has already been called.
60
+
61
+ """
62
+ if self._client is not None:
63
+ raise RuntimeError(
64
+ "SMLIGHTConnectionManager.start() has already been called; "
65
+ "create a new manager instance to reconnect."
66
+ )
67
+ data = connect_scanner(self._source, self._name, self._host, self._port)
68
+ scanner = data.scanner
69
+ self._unsetup_scanner = scanner.async_setup()
70
+ self._unregister_scanner = get_manager().async_register_scanner(scanner)
71
+ self._client = data.client
72
+ await self._client.start()
73
+
74
+ async def stop(self) -> None:
75
+ """Stop the BLE proxy client and unregister the scanner."""
76
+ if self._client is not None:
77
+ self._client.stop()
78
+ self._client = None
79
+ if self._unregister_scanner is not None:
80
+ self._unregister_scanner()
81
+ self._unregister_scanner = None
82
+ if self._unsetup_scanner is not None:
83
+ self._unsetup_scanner()
84
+ self._unsetup_scanner = None
File without changes