matter-ble-proxy 0.7.1__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.
- matter_ble_proxy-0.7.1/PKG-INFO +128 -0
- matter_ble_proxy-0.7.1/README.md +97 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy/__init__.py +40 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy/bleak_backend.py +108 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy/cli.py +105 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy/client.py +688 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy/protocol.py +63 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy/py.typed +0 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy.egg-info/PKG-INFO +128 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy.egg-info/SOURCES.txt +16 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy.egg-info/dependency_links.txt +1 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy.egg-info/entry_points.txt +2 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy.egg-info/not-zip-safe +1 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy.egg-info/requires.txt +13 -0
- matter_ble_proxy-0.7.1/matter_ble_proxy.egg-info/top_level.txt +1 -0
- matter_ble_proxy-0.7.1/pyproject.toml +212 -0
- matter_ble_proxy-0.7.1/setup.cfg +4 -0
- matter_ble_proxy-0.7.1/tests/test_protocol.py +62 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matter-ble-proxy
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Summary: Python client library for the OHF Matter Server BLE proxy protocol
|
|
5
|
+
Author-email: Open Home Foundation <hello@openhomefoundation.io>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/matter-js/matterjs-server
|
|
8
|
+
Project-URL: Source, https://github.com/matter-js/matterjs-server/tree/main/python_ble_proxy
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/matter-js/matterjs-server/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/matter-js/matterjs-server/blob/main/CHANGELOG.md
|
|
11
|
+
Platform: any
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Home Automation
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: aiohttp
|
|
20
|
+
Requires-Dist: bleak
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: codespell==2.4.1; extra == "test"
|
|
23
|
+
Requires-Dist: isort==7.0.0; extra == "test"
|
|
24
|
+
Requires-Dist: mypy==1.19.1; extra == "test"
|
|
25
|
+
Requires-Dist: pylint==4.0.4; extra == "test"
|
|
26
|
+
Requires-Dist: pytest>=9.0; extra == "test"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-aiohttp>=1.0; extra == "test"
|
|
29
|
+
Requires-Dist: pytest-cov>=7.0; extra == "test"
|
|
30
|
+
Requires-Dist: ruff==0.14.9; extra == "test"
|
|
31
|
+
|
|
32
|
+
# matter-ble-proxy
|
|
33
|
+
|
|
34
|
+
Python client library for the [OHF Matter Server](https://github.com/matter-js/matterjs-server)
|
|
35
|
+
BLE proxy WebSocket protocol.
|
|
36
|
+
|
|
37
|
+
The matter-server can run on a host with no BLE adapter and delegate every BLE
|
|
38
|
+
operation to a separate process or device. This library implements the client
|
|
39
|
+
side of that protocol so that any Python process with access to a BLE adapter
|
|
40
|
+
(via [Bleak](https://github.com/hbldh/bleak), Home Assistant's bluetooth
|
|
41
|
+
component, an ESPHome BLE proxy, ...) can act as the BLE bridge.
|
|
42
|
+
|
|
43
|
+
The protocol is documented in [`docs/ble-proxy-protocol.md`](https://github.com/matter-js/matterjs-server/blob/main/docs/ble-proxy-protocol.md).
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install matter-ble-proxy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Python 3.12+ required.
|
|
52
|
+
|
|
53
|
+
## Standalone CLI
|
|
54
|
+
|
|
55
|
+
The package ships a reference CLI mirroring the JS `noble-ble-proxy` example.
|
|
56
|
+
Useful for testing the matter-server's `/ble` endpoint without Home Assistant
|
|
57
|
+
in the loop.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Start the matter-server with --ble-proxy in one terminal, then:
|
|
61
|
+
matter-ble-proxy --server ws://localhost:5580/ble
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The CLI uses Bleak directly against the local OS bluetooth adapter.
|
|
65
|
+
|
|
66
|
+
## Library API
|
|
67
|
+
|
|
68
|
+
For integrators (Home Assistant, custom add-ons, etc.) wire your own BLE source
|
|
69
|
+
in by implementing two ABCs:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from matter_ble_proxy import (
|
|
73
|
+
AdvertisementData,
|
|
74
|
+
BleDeviceResolver,
|
|
75
|
+
BleScanSource,
|
|
76
|
+
MatterBleProxy,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
class MyScanSource(BleScanSource):
|
|
80
|
+
async def start(self, callback): ... # call `callback(AdvertisementData(...))`
|
|
81
|
+
async def stop(self): ...
|
|
82
|
+
|
|
83
|
+
class MyDeviceResolver(BleDeviceResolver):
|
|
84
|
+
async def resolve(self, address): ... # return a bleak.BLEDevice / address / None
|
|
85
|
+
|
|
86
|
+
proxy = MatterBleProxy(
|
|
87
|
+
ws_url="ws://localhost:5580/ble",
|
|
88
|
+
scan_source=MyScanSource(),
|
|
89
|
+
device_resolver=MyDeviceResolver(),
|
|
90
|
+
)
|
|
91
|
+
await proxy.connect()
|
|
92
|
+
await proxy.run_until_closed()
|
|
93
|
+
await proxy.disconnect()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The default Bleak-backed implementations (`BleakScanSource`,
|
|
97
|
+
`BleakDeviceResolver`) live in `matter_ble_proxy.bleak_backend`.
|
|
98
|
+
|
|
99
|
+
### Reconnection
|
|
100
|
+
|
|
101
|
+
`MatterBleProxy` does not reconnect on its own. When the WebSocket closes — server
|
|
102
|
+
restart, network blip, or the caller calling `disconnect()` — `run_until_closed()`
|
|
103
|
+
returns after the library releases all BLE resources (active scan stopped, every
|
|
104
|
+
peripheral disconnected). The caller decides whether to reconnect:
|
|
105
|
+
|
|
106
|
+
- The bundled CLI exits on disconnect; restart it manually.
|
|
107
|
+
- Home Assistant ties the BLE proxy lifecycle to the matter-server WebSocket: when
|
|
108
|
+
HA reconnects to the matter-server it constructs and connects a fresh
|
|
109
|
+
`MatterBleProxy` for the new session.
|
|
110
|
+
- A custom integration can wrap `connect()` + `run_until_closed()` in a retry loop
|
|
111
|
+
with whatever backoff and cancellation policy fits its supervisor.
|
|
112
|
+
|
|
113
|
+
The library deliberately stays out of this decision so it can plug into hosts
|
|
114
|
+
that already own reconnect logic (HA, systemd, etc.) without fighting them.
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
This package lives inside the
|
|
119
|
+
[matter-js/matterjs-server](https://github.com/matter-js/matterjs-server) repo
|
|
120
|
+
and shares its release pipeline. From the repo root:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm run python-ble-proxy:install # create venv + install editable + test deps
|
|
124
|
+
npm run python-ble-proxy:lint # ruff
|
|
125
|
+
npm run python-ble-proxy:typecheck # mypy
|
|
126
|
+
npm run python-ble-proxy:test # pytest
|
|
127
|
+
npm run python-ble-proxy:build # build sdist+wheel
|
|
128
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# matter-ble-proxy
|
|
2
|
+
|
|
3
|
+
Python client library for the [OHF Matter Server](https://github.com/matter-js/matterjs-server)
|
|
4
|
+
BLE proxy WebSocket protocol.
|
|
5
|
+
|
|
6
|
+
The matter-server can run on a host with no BLE adapter and delegate every BLE
|
|
7
|
+
operation to a separate process or device. This library implements the client
|
|
8
|
+
side of that protocol so that any Python process with access to a BLE adapter
|
|
9
|
+
(via [Bleak](https://github.com/hbldh/bleak), Home Assistant's bluetooth
|
|
10
|
+
component, an ESPHome BLE proxy, ...) can act as the BLE bridge.
|
|
11
|
+
|
|
12
|
+
The protocol is documented in [`docs/ble-proxy-protocol.md`](https://github.com/matter-js/matterjs-server/blob/main/docs/ble-proxy-protocol.md).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install matter-ble-proxy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Python 3.12+ required.
|
|
21
|
+
|
|
22
|
+
## Standalone CLI
|
|
23
|
+
|
|
24
|
+
The package ships a reference CLI mirroring the JS `noble-ble-proxy` example.
|
|
25
|
+
Useful for testing the matter-server's `/ble` endpoint without Home Assistant
|
|
26
|
+
in the loop.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Start the matter-server with --ble-proxy in one terminal, then:
|
|
30
|
+
matter-ble-proxy --server ws://localhost:5580/ble
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The CLI uses Bleak directly against the local OS bluetooth adapter.
|
|
34
|
+
|
|
35
|
+
## Library API
|
|
36
|
+
|
|
37
|
+
For integrators (Home Assistant, custom add-ons, etc.) wire your own BLE source
|
|
38
|
+
in by implementing two ABCs:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from matter_ble_proxy import (
|
|
42
|
+
AdvertisementData,
|
|
43
|
+
BleDeviceResolver,
|
|
44
|
+
BleScanSource,
|
|
45
|
+
MatterBleProxy,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
class MyScanSource(BleScanSource):
|
|
49
|
+
async def start(self, callback): ... # call `callback(AdvertisementData(...))`
|
|
50
|
+
async def stop(self): ...
|
|
51
|
+
|
|
52
|
+
class MyDeviceResolver(BleDeviceResolver):
|
|
53
|
+
async def resolve(self, address): ... # return a bleak.BLEDevice / address / None
|
|
54
|
+
|
|
55
|
+
proxy = MatterBleProxy(
|
|
56
|
+
ws_url="ws://localhost:5580/ble",
|
|
57
|
+
scan_source=MyScanSource(),
|
|
58
|
+
device_resolver=MyDeviceResolver(),
|
|
59
|
+
)
|
|
60
|
+
await proxy.connect()
|
|
61
|
+
await proxy.run_until_closed()
|
|
62
|
+
await proxy.disconnect()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The default Bleak-backed implementations (`BleakScanSource`,
|
|
66
|
+
`BleakDeviceResolver`) live in `matter_ble_proxy.bleak_backend`.
|
|
67
|
+
|
|
68
|
+
### Reconnection
|
|
69
|
+
|
|
70
|
+
`MatterBleProxy` does not reconnect on its own. When the WebSocket closes — server
|
|
71
|
+
restart, network blip, or the caller calling `disconnect()` — `run_until_closed()`
|
|
72
|
+
returns after the library releases all BLE resources (active scan stopped, every
|
|
73
|
+
peripheral disconnected). The caller decides whether to reconnect:
|
|
74
|
+
|
|
75
|
+
- The bundled CLI exits on disconnect; restart it manually.
|
|
76
|
+
- Home Assistant ties the BLE proxy lifecycle to the matter-server WebSocket: when
|
|
77
|
+
HA reconnects to the matter-server it constructs and connects a fresh
|
|
78
|
+
`MatterBleProxy` for the new session.
|
|
79
|
+
- A custom integration can wrap `connect()` + `run_until_closed()` in a retry loop
|
|
80
|
+
with whatever backoff and cancellation policy fits its supervisor.
|
|
81
|
+
|
|
82
|
+
The library deliberately stays out of this decision so it can plug into hosts
|
|
83
|
+
that already own reconnect logic (HA, systemd, etc.) without fighting them.
|
|
84
|
+
|
|
85
|
+
## Development
|
|
86
|
+
|
|
87
|
+
This package lives inside the
|
|
88
|
+
[matter-js/matterjs-server](https://github.com/matter-js/matterjs-server) repo
|
|
89
|
+
and shares its release pipeline. From the repo root:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm run python-ble-proxy:install # create venv + install editable + test deps
|
|
93
|
+
npm run python-ble-proxy:lint # ruff
|
|
94
|
+
npm run python-ble-proxy:typecheck # mypy
|
|
95
|
+
npm run python-ble-proxy:test # pytest
|
|
96
|
+
npm run python-ble-proxy:build # build sdist+wheel
|
|
97
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Python client library for the OHF Matter Server BLE proxy protocol.
|
|
2
|
+
|
|
3
|
+
This package implements the client side of the BLE proxy WebSocket protocol
|
|
4
|
+
exposed by the matter-server's `/ble` endpoint. It bridges a Matter
|
|
5
|
+
commissioning controller running on the server to a local BLE adapter on the
|
|
6
|
+
client side (Bleak directly, Home Assistant's bluetooth component, ESPHome BLE
|
|
7
|
+
proxies, etc.).
|
|
8
|
+
|
|
9
|
+
The core protocol logic lives in :mod:`matter_ble_proxy.client` and is BLE-
|
|
10
|
+
transport-agnostic via the :class:`BleScanSource` and :class:`BleDeviceResolver`
|
|
11
|
+
abstractions. A default :class:`BleakScanSource` + :class:`BleakDeviceResolver`
|
|
12
|
+
implementation is provided in :mod:`matter_ble_proxy.bleak_backend` for
|
|
13
|
+
standalone use; integrators (e.g. Home Assistant) supply their own backend.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .bleak_backend import BleakDeviceResolver, BleakScanSource
|
|
17
|
+
from .client import BleDeviceResolver, BleScanSource, ConnectionState, MatterBleProxy
|
|
18
|
+
from .protocol import (
|
|
19
|
+
BINARY_FRAME_HEADER,
|
|
20
|
+
BLE_PROXY_PROTOCOL_VERSION,
|
|
21
|
+
OPCODE_NOTIFICATION,
|
|
22
|
+
OPCODE_READ_RESPONSE,
|
|
23
|
+
OPCODE_WRITE_DATA,
|
|
24
|
+
AdvertisementData,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"BINARY_FRAME_HEADER",
|
|
29
|
+
"BLE_PROXY_PROTOCOL_VERSION",
|
|
30
|
+
"OPCODE_NOTIFICATION",
|
|
31
|
+
"OPCODE_READ_RESPONSE",
|
|
32
|
+
"OPCODE_WRITE_DATA",
|
|
33
|
+
"AdvertisementData",
|
|
34
|
+
"BleDeviceResolver",
|
|
35
|
+
"BleScanSource",
|
|
36
|
+
"BleakDeviceResolver",
|
|
37
|
+
"BleakScanSource",
|
|
38
|
+
"ConnectionState",
|
|
39
|
+
"MatterBleProxy",
|
|
40
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Default Bleak-based backend for the BLE proxy client.
|
|
2
|
+
|
|
3
|
+
Use these when the host process owns its own BLE adapter directly via Bleak
|
|
4
|
+
(CLI tools, integration tests, single-tenant servers). Home Assistant supplies
|
|
5
|
+
a different backend that wires into its bluetooth component.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from bleak import BleakScanner
|
|
14
|
+
|
|
15
|
+
from .client import BleDeviceResolver, BleScanSource
|
|
16
|
+
from .protocol import AdvertisementData
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
|
|
21
|
+
from bleak.backends.device import BLEDevice
|
|
22
|
+
from bleak.backends.scanner import AdvertisementData as BleakAdvertisementData
|
|
23
|
+
|
|
24
|
+
_LOGGER = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BleakScanSource(BleScanSource):
|
|
28
|
+
"""Scan source backed by a directly-managed `BleakScanner`.
|
|
29
|
+
|
|
30
|
+
The scanner is created on each :meth:`start` and torn down on :meth:`stop`,
|
|
31
|
+
so the BLE adapter sits idle whenever the matter-server is not actively
|
|
32
|
+
scanning. The first `start_scan` after a process boot pays the native
|
|
33
|
+
cold-start cost (CoreBluetooth state transition to `powered_on`, DBus
|
|
34
|
+
handshake) — typically tens to hundreds of milliseconds.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
"""Initialize."""
|
|
39
|
+
self._scanner: BleakScanner | None = None
|
|
40
|
+
self._callback: Callable[[AdvertisementData], None] | None = None
|
|
41
|
+
# Cache the most recent BLEDevice per address so BleakDeviceResolver
|
|
42
|
+
# can hand a fully-formed device to BleakClient (more reliable than
|
|
43
|
+
# connecting by address alone on some platforms).
|
|
44
|
+
self._device_cache: dict[str, BLEDevice] = {}
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def device_cache(self) -> dict[str, BLEDevice]:
|
|
48
|
+
"""Read-only access for paired :class:`BleakDeviceResolver`."""
|
|
49
|
+
return self._device_cache
|
|
50
|
+
|
|
51
|
+
async def start(self, callback: Callable[[AdvertisementData], None]) -> None:
|
|
52
|
+
"""Start the scanner if not already running."""
|
|
53
|
+
self._callback = callback
|
|
54
|
+
if self._scanner is not None:
|
|
55
|
+
return
|
|
56
|
+
self._scanner = BleakScanner(detection_callback=self._on_detection)
|
|
57
|
+
await self._scanner.start()
|
|
58
|
+
|
|
59
|
+
async def stop(self) -> None:
|
|
60
|
+
"""Stop the scanner and release the BLE adapter."""
|
|
61
|
+
scanner = self._scanner
|
|
62
|
+
self._scanner = None
|
|
63
|
+
self._callback = None
|
|
64
|
+
if scanner is not None:
|
|
65
|
+
try:
|
|
66
|
+
await scanner.stop()
|
|
67
|
+
except Exception:
|
|
68
|
+
_LOGGER.debug("Error stopping BleakScanner", exc_info=True)
|
|
69
|
+
|
|
70
|
+
def _on_detection(self, device: BLEDevice, advertisement: BleakAdvertisementData) -> None:
|
|
71
|
+
self._device_cache[device.address] = device
|
|
72
|
+
cb = self._callback
|
|
73
|
+
if cb is None:
|
|
74
|
+
return
|
|
75
|
+
ad = AdvertisementData(
|
|
76
|
+
address=device.address,
|
|
77
|
+
name=advertisement.local_name or device.name,
|
|
78
|
+
rssi=advertisement.rssi,
|
|
79
|
+
connectable=True, # Bleak does not expose this directly; assume true.
|
|
80
|
+
service_data=dict(advertisement.service_data),
|
|
81
|
+
manufacturer_data=dict(advertisement.manufacturer_data),
|
|
82
|
+
service_uuids=list(advertisement.service_uuids),
|
|
83
|
+
)
|
|
84
|
+
try:
|
|
85
|
+
cb(ad)
|
|
86
|
+
except Exception:
|
|
87
|
+
_LOGGER.exception("BLE proxy advertisement callback raised")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class BleakDeviceResolver(BleDeviceResolver):
|
|
91
|
+
"""Default resolver: prefer a cached `BLEDevice`, fall back to the address.
|
|
92
|
+
|
|
93
|
+
When paired with :class:`BleakScanSource`, the resolver reuses the device
|
|
94
|
+
object the scanner observed. Without a paired source it returns the raw
|
|
95
|
+
address — Bleak then performs its own short scan inside `connect()`.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, scan_source: BleakScanSource | None = None) -> None:
|
|
99
|
+
"""Initialize."""
|
|
100
|
+
self._scan_source = scan_source
|
|
101
|
+
|
|
102
|
+
async def resolve(self, address: str) -> BLEDevice | str | None:
|
|
103
|
+
"""Return cached `BLEDevice` if available, else the address."""
|
|
104
|
+
if self._scan_source is not None:
|
|
105
|
+
device = self._scan_source.device_cache.get(address)
|
|
106
|
+
if device is not None:
|
|
107
|
+
return device
|
|
108
|
+
return address
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Reference Bleak-based CLI for the matter-ble-proxy client library.
|
|
2
|
+
|
|
3
|
+
Connects to a matter-server `/ble` WebSocket endpoint and bridges a local
|
|
4
|
+
Bleak adapter into it. Useful for:
|
|
5
|
+
|
|
6
|
+
- exercising the BLE proxy protocol end-to-end without Home Assistant
|
|
7
|
+
- reproducing protocol bugs against a controlled BLE adapter
|
|
8
|
+
- smoke-testing matter-server BLE commissioning from another machine
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
matter-ble-proxy --server ws://localhost:5580/ble
|
|
12
|
+
|
|
13
|
+
The same flag set as the JS `noble-ble-proxy` example.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import asyncio
|
|
20
|
+
import contextlib
|
|
21
|
+
import logging
|
|
22
|
+
import signal
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
from matter_ble_proxy.bleak_backend import BleakDeviceResolver, BleakScanSource
|
|
26
|
+
from matter_ble_proxy.client import MatterBleProxy
|
|
27
|
+
|
|
28
|
+
_LOGGER = logging.getLogger("matter_ble_proxy.cli")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _parse_args(argv: list[str] | None) -> argparse.Namespace:
|
|
32
|
+
parser = argparse.ArgumentParser(
|
|
33
|
+
description="Bleak-based BLE proxy client for matter-server's /ble endpoint",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--server",
|
|
37
|
+
default="ws://localhost:5580/ble",
|
|
38
|
+
help="matter-server BLE proxy URL (default: %(default)s)",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--log-level",
|
|
42
|
+
default="INFO",
|
|
43
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
44
|
+
help="Logging level (default: %(default)s)",
|
|
45
|
+
)
|
|
46
|
+
return parser.parse_args(argv)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def _run(server_url: str) -> int:
|
|
50
|
+
scan_source = BleakScanSource()
|
|
51
|
+
device_resolver = BleakDeviceResolver(scan_source)
|
|
52
|
+
proxy = MatterBleProxy(server_url, scan_source, device_resolver)
|
|
53
|
+
|
|
54
|
+
_LOGGER.info("Connecting to %s...", server_url)
|
|
55
|
+
try:
|
|
56
|
+
await proxy.connect()
|
|
57
|
+
except ConnectionError:
|
|
58
|
+
_LOGGER.exception("Failed to connect")
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
_LOGGER.info("Connected. BLE proxy active. Press Ctrl+C to stop.")
|
|
62
|
+
|
|
63
|
+
stop_event = asyncio.Event()
|
|
64
|
+
|
|
65
|
+
def _request_stop() -> None:
|
|
66
|
+
_LOGGER.info("Shutting down...")
|
|
67
|
+
stop_event.set()
|
|
68
|
+
|
|
69
|
+
loop = asyncio.get_running_loop()
|
|
70
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
71
|
+
# add_signal_handler raises NotImplementedError on Windows and inside
|
|
72
|
+
# some restricted async runtimes; fall back to KeyboardInterrupt there.
|
|
73
|
+
with contextlib.suppress(NotImplementedError):
|
|
74
|
+
loop.add_signal_handler(sig, _request_stop)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
await asyncio.wait(
|
|
78
|
+
[
|
|
79
|
+
asyncio.create_task(proxy.run_until_closed()),
|
|
80
|
+
asyncio.create_task(stop_event.wait()),
|
|
81
|
+
],
|
|
82
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
83
|
+
)
|
|
84
|
+
finally:
|
|
85
|
+
await proxy.disconnect()
|
|
86
|
+
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main(argv: list[str] | None = None) -> int:
|
|
91
|
+
"""Console entry point for the `matter-ble-proxy` script."""
|
|
92
|
+
args = _parse_args(argv)
|
|
93
|
+
logging.basicConfig(
|
|
94
|
+
level=args.log_level,
|
|
95
|
+
format="%(asctime)s.%(msecs)03d %(levelname)s %(name)s: %(message)s",
|
|
96
|
+
datefmt="%H:%M:%S",
|
|
97
|
+
)
|
|
98
|
+
try:
|
|
99
|
+
return asyncio.run(_run(args.server))
|
|
100
|
+
except KeyboardInterrupt:
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
sys.exit(main())
|