entr-ble 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.
entr_ble-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johann Lejeune
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: entr-ble
3
+ Version: 1.0.0
4
+ Summary: Python client for ENTR Bluetooth locks
5
+ Author: Johann Lejeune
6
+ Author-email: Johann Lejeune <tofupedia.fr@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Dist: bleak>=3.0.2
10
+ Requires-Dist: cryptography>=48.0.1
11
+ Requires-Python: >=3.14
12
+ Project-URL: Repository, https://github.com/johannlejeune/entr-ble
13
+ Project-URL: Issues, https://github.com/johannlejeune/entr-ble/issues
14
+ Description-Content-Type: text/markdown
15
+
16
+ # ENTR BLE
17
+
18
+ **Your ENTR lock, over Bluetooth.** A Python library, a command-line interface, and a Home Assistant integration sharing the same protocol implementation.
19
+
20
+ > [!WARNING]
21
+ > This project is fully vibe coded, but it is being used in a working Home Assistant installation.\
22
+ > It was created to integrate the lock into Home Assistant and get rid of the horrible mobile app.
23
+
24
+ [![GitHub release](https://img.shields.io/github/v/release/johannlejeune/entr-ble)](https://github.com/johannlejeune/entr-ble/releases/latest)
25
+ [![Checks](https://github.com/johannlejeune/entr-ble/actions/workflows/check.yml/badge.svg)](https://github.com/johannlejeune/entr-ble/actions/workflows/check.yml)
26
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
27
+
28
+ ## Choose your starting point
29
+
30
+ | Use ENTR BLE to… | Start here | Component |
31
+ | --- | --- | --- |
32
+ | Control a lock from dashboards and automations | [Home Assistant guide](custom_components/entr_ble/README.md) | HACS custom integration |
33
+ | Set up a lock, manage keys, or control it from a terminal | [CLI guide](packages/entr-ble-cli/README.md) | `entr-ble-cli` command |
34
+ | Build your own application | [Python library guide](src/entr_ble/README.md) | `entr-ble` → `EntrLockClient` |
35
+
36
+ The library is independent of the CLI and Home Assistant. Both frontends use its asynchronous API for pairing, encrypted sessions, and lock operations.
37
+
38
+ ## What you can do
39
+
40
+ - Discover nearby locks and initialize a new lock or recover owner access.
41
+ - Lock and unlock, read battery and reported door/lock status.
42
+ - Create, activate, suspend, and revoke user keys.
43
+ - Change settings, calibrate the lock and magnet, and read diagnostics on supported firmware.
44
+ - Import CLI credentials into Home Assistant without enrolling again.
45
+
46
+ See [feature coverage](docs/FEATURES.md) for the complete command matrix and firmware requirements, and [protocol notes](docs/PROTOCOL.md) for the underlying BLE protocol.
47
+
48
+ ## Before you start
49
+
50
+ The Python packages require **Python 3.14+** and a working Bluetooth adapter; the HACS integration requires **Home Assistant 2026.9.3+** and connectable Bluetooth coverage.
51
+
52
+ ENTR has a single owner slot. Recovering ownership replaces the previous owner's credential. To keep the mobile app as owner, redeem an additional user key instead. Each component's guide explains the choice before setup.
53
+
54
+ Hardware observations come from an **ENTR EURO with communication version 1.29r5**. Firmware support varies, and reported state can be stale after manual operation. This is an independent project, unaffiliated with ASSA ABLOY.
55
+
56
+ ## Development
57
+
58
+ This repository is a uv workspace:
59
+
60
+ ```text
61
+ src/entr_ble/ Python library
62
+ packages/entr-ble-cli/ CLI and local credential store
63
+ custom_components/entr_ble/ Home Assistant integration
64
+ docs/ Feature coverage and protocol reference
65
+ ```
66
+
67
+ From the repository root, install all packages and the isolated Home Assistant development dependencies. The full suite requires Python 3.14.2+.
68
+
69
+ ```sh
70
+ uv sync --locked --all-packages --group hacs
71
+ uv run --no-sync ruff check .
72
+ uv run --no-sync ruff format --check .
73
+ uv run --no-sync basedpyright
74
+ uv run --no-sync python -m unittest discover -s tests -v
75
+ uv run --no-sync python -m unittest discover -s packages/entr-ble-cli/tests -v
76
+ uv build --all-packages --no-sources
77
+ ```
78
+
79
+ Tests simulate BLE responses and do not operate a physical lock. The [CI workflow](.github/workflows/check.yml) also validates the integration with Hassfest and HACS. Builds produce separate library and CLI wheels and source distributions in `dist/`; hardware behavior still needs verification on the target lock.
80
+
81
+ ## Releases
82
+
83
+ All components release together with one version and [changelog](CHANGELOG.md). Release Please prepares a release PR; merging it runs the checks, publishes both Python packages to PyPI, and publishes the matching GitHub release for HACS. See [release setup and recovery](docs/RELEASING.md).
84
+
85
+ ## Support and license
86
+
87
+ [Report an issue](https://github.com/johannlejeune/entr-ble/issues) with the component, software version, lock model, communication version if known, and the command or action that failed. Remove credentials and passwords from anything you share.
88
+
89
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,74 @@
1
+ # ENTR BLE
2
+
3
+ **Your ENTR lock, over Bluetooth.** A Python library, a command-line interface, and a Home Assistant integration sharing the same protocol implementation.
4
+
5
+ > [!WARNING]
6
+ > This project is fully vibe coded, but it is being used in a working Home Assistant installation.\
7
+ > It was created to integrate the lock into Home Assistant and get rid of the horrible mobile app.
8
+
9
+ [![GitHub release](https://img.shields.io/github/v/release/johannlejeune/entr-ble)](https://github.com/johannlejeune/entr-ble/releases/latest)
10
+ [![Checks](https://github.com/johannlejeune/entr-ble/actions/workflows/check.yml/badge.svg)](https://github.com/johannlejeune/entr-ble/actions/workflows/check.yml)
11
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
12
+
13
+ ## Choose your starting point
14
+
15
+ | Use ENTR BLE to… | Start here | Component |
16
+ | --- | --- | --- |
17
+ | Control a lock from dashboards and automations | [Home Assistant guide](custom_components/entr_ble/README.md) | HACS custom integration |
18
+ | Set up a lock, manage keys, or control it from a terminal | [CLI guide](packages/entr-ble-cli/README.md) | `entr-ble-cli` command |
19
+ | Build your own application | [Python library guide](src/entr_ble/README.md) | `entr-ble` → `EntrLockClient` |
20
+
21
+ The library is independent of the CLI and Home Assistant. Both frontends use its asynchronous API for pairing, encrypted sessions, and lock operations.
22
+
23
+ ## What you can do
24
+
25
+ - Discover nearby locks and initialize a new lock or recover owner access.
26
+ - Lock and unlock, read battery and reported door/lock status.
27
+ - Create, activate, suspend, and revoke user keys.
28
+ - Change settings, calibrate the lock and magnet, and read diagnostics on supported firmware.
29
+ - Import CLI credentials into Home Assistant without enrolling again.
30
+
31
+ See [feature coverage](docs/FEATURES.md) for the complete command matrix and firmware requirements, and [protocol notes](docs/PROTOCOL.md) for the underlying BLE protocol.
32
+
33
+ ## Before you start
34
+
35
+ The Python packages require **Python 3.14+** and a working Bluetooth adapter; the HACS integration requires **Home Assistant 2026.9.3+** and connectable Bluetooth coverage.
36
+
37
+ ENTR has a single owner slot. Recovering ownership replaces the previous owner's credential. To keep the mobile app as owner, redeem an additional user key instead. Each component's guide explains the choice before setup.
38
+
39
+ Hardware observations come from an **ENTR EURO with communication version 1.29r5**. Firmware support varies, and reported state can be stale after manual operation. This is an independent project, unaffiliated with ASSA ABLOY.
40
+
41
+ ## Development
42
+
43
+ This repository is a uv workspace:
44
+
45
+ ```text
46
+ src/entr_ble/ Python library
47
+ packages/entr-ble-cli/ CLI and local credential store
48
+ custom_components/entr_ble/ Home Assistant integration
49
+ docs/ Feature coverage and protocol reference
50
+ ```
51
+
52
+ From the repository root, install all packages and the isolated Home Assistant development dependencies. The full suite requires Python 3.14.2+.
53
+
54
+ ```sh
55
+ uv sync --locked --all-packages --group hacs
56
+ uv run --no-sync ruff check .
57
+ uv run --no-sync ruff format --check .
58
+ uv run --no-sync basedpyright
59
+ uv run --no-sync python -m unittest discover -s tests -v
60
+ uv run --no-sync python -m unittest discover -s packages/entr-ble-cli/tests -v
61
+ uv build --all-packages --no-sources
62
+ ```
63
+
64
+ Tests simulate BLE responses and do not operate a physical lock. The [CI workflow](.github/workflows/check.yml) also validates the integration with Hassfest and HACS. Builds produce separate library and CLI wheels and source distributions in `dist/`; hardware behavior still needs verification on the target lock.
65
+
66
+ ## Releases
67
+
68
+ All components release together with one version and [changelog](CHANGELOG.md). Release Please prepares a release PR; merging it runs the checks, publishes both Python packages to PyPI, and publishes the matching GitHub release for HACS. See [release setup and recovery](docs/RELEASING.md).
69
+
70
+ ## Support and license
71
+
72
+ [Report an issue](https://github.com/johannlejeune/entr-ble/issues) with the component, software version, lock model, communication version if known, and the command or action that failed. Remove credentials and passwords from anything you share.
73
+
74
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "entr-ble"
3
+ version = "1.0.0"
4
+ description = "Python client for ENTR Bluetooth locks"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14"
9
+ dependencies = [
10
+ "bleak>=3.0.2",
11
+ "cryptography>=48.0.1",
12
+ ]
13
+
14
+ [[project.authors]]
15
+ name = "Johann Lejeune"
16
+ email = "tofupedia.fr@gmail.com"
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/johannlejeune/entr-ble"
20
+ Issues = "https://github.com/johannlejeune/entr-ble/issues"
21
+
22
+ [tool.ruff.lint.per-file-ignores]
23
+ "src/entr_ble/client/*.py" = ["PLR0402"]
24
+
25
+ [tool.basedpyright]
26
+ typeCheckingMode = "standard"
27
+ include = [
28
+ "src",
29
+ "packages/entr-ble-cli/src",
30
+ "custom_components",
31
+ "tests",
32
+ "packages/entr-ble-cli/tests",
33
+ ]
34
+ extraPaths = [
35
+ "src",
36
+ "packages/entr-ble-cli/src",
37
+ ".",
38
+ ]
39
+
40
+ [tool.uv.workspace]
41
+ members = ["packages/entr-ble-cli"]
42
+
43
+ [tool.uv.dependency-groups.hacs]
44
+ requires-python = ">=3.14.2"
45
+
46
+ [build-system]
47
+ requires = ["uv_build>=0.12.0,<0.13.0"]
48
+ build-backend = "uv_build"
49
+
50
+ [dependency-groups]
51
+ dev = [
52
+ "basedpyright>=1.40.1",
53
+ "ruff>=0.16.9",
54
+ ]
55
+ hacs = ["homeassistant==2026.9.4"]
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "entr-ble"
3
+ version = "1.0.0"
4
+ description = "Python client for ENTR Bluetooth locks"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Johann Lejeune", email = "tofupedia.fr@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.14"
12
+ dependencies = [
13
+ "bleak>=3.0.2",
14
+ "cryptography>=48.0.1",
15
+ ]
16
+
17
+ [project.urls]
18
+ Repository = "https://github.com/johannlejeune/entr-ble"
19
+ Issues = "https://github.com/johannlejeune/entr-ble/issues"
20
+
21
+ [tool.ruff.lint.per-file-ignores]
22
+ "src/entr_ble/client/*.py" = ["PLR0402"]
23
+
24
+ [tool.basedpyright]
25
+ typeCheckingMode = "standard"
26
+ include = ["src", "packages/entr-ble-cli/src", "custom_components", "tests", "packages/entr-ble-cli/tests"]
27
+ extraPaths = ["src", "packages/entr-ble-cli/src", "."]
28
+
29
+ [tool.uv.workspace]
30
+ members = ["packages/entr-ble-cli"]
31
+
32
+ [tool.uv.dependency-groups]
33
+ hacs = { requires-python = ">=3.14.2" }
34
+
35
+ [build-system]
36
+ requires = ["uv_build>=0.12.0,<0.13.0"]
37
+ build-backend = "uv_build"
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "basedpyright>=1.40.1",
42
+ "ruff>=0.16.9",
43
+ ]
44
+ hacs = [
45
+ "homeassistant==2026.9.4",
46
+ ]
@@ -0,0 +1,110 @@
1
+ # ENTR BLE · Python library
2
+
3
+ **An asynchronous Python client for ENTR Bluetooth locks.** Discover locks, provision credentials, restore encrypted sessions, and send lock commands using Bleak.
4
+
5
+ [Project overview](../../README.md) · [CLI](../../packages/entr-ble-cli/README.md) · [Home Assistant](../../custom_components/entr_ble/README.md) · [Protocol reference](../../docs/PROTOCOL.md)
6
+
7
+ ## Installation
8
+
9
+ Requires **Python 3.14+** and a working Bluetooth adapter. On Linux, Bluetooth access uses BlueZ and the system D-Bus.
10
+
11
+ Add the library to your project:
12
+
13
+ ```sh
14
+ uv add entr-ble
15
+ ```
16
+
17
+ For environments using pip, use `pip install entr-ble`.
18
+
19
+ From a checkout, install the library into the repository's virtual environment:
20
+
21
+ ```sh
22
+ uv sync --locked
23
+ ```
24
+
25
+ To install directly from the repository into your own environment:
26
+
27
+ ```sh
28
+ pip install "git+https://github.com/johannlejeune/entr-ble.git"
29
+ ```
30
+
31
+ The package is named `entr-ble`; import it as `entr_ble`. For a terminal command and credential storage, install the separate [CLI](../../packages/entr-ble-cli/README.md).
32
+
33
+ ## Read status with an existing key
34
+
35
+ `EntrLockClient` accepts a Bluetooth address or a Bleak `BLEDevice`. Connect, read the communication version, then restore the saved session before issuing encrypted commands:
36
+
37
+ ```python
38
+ from entr_ble import EntrLockClient
39
+
40
+
41
+ async def read_status(address, kdf_id, role, aes_key):
42
+ client = EntrLockClient(address)
43
+ try:
44
+ await client.connect()
45
+ await client.fetch_comm_version()
46
+ await client.kdf_resync(kdf_id, role, aes_key)
47
+ return await client.get_device_config()
48
+ finally:
49
+ await client.disconnect()
50
+ ```
51
+
52
+ Call this function from your application's event loop. `aes_key` is the saved **16-byte key**; decode hexadecimal storage with `bytes.fromhex()` before passing it. `kdf_id` and `role` are the values returned during provisioning.
53
+
54
+ Locking and unlocking also require the saved user id, application id, and BLE lock key. Within the restored session, use:
55
+
56
+ ```python
57
+ await client.unlock(user_id, app_id, ble_ekey)
58
+ await client.lock(user_id, app_id, ble_ekey)
59
+ ```
60
+
61
+ The ids are each 16 bytes, and `ble_ekey` is 32 bytes. Keep one command in flight per client and disconnect after use. After reconnecting, restore the session again.
62
+
63
+ ## Provisioning and credentials
64
+
65
+ For a new credential, the protocol starts with `pair()` and `handshake(app_id)`, followed by `set_owner()`, `recover_owner()`, or `get_new_key()`. These methods acknowledge provisioning responses; your application must persist the returned credential fields and the session key.
66
+
67
+ **Recovering the owner slot revokes the previous owner's credential.** Redeem an additional user key to preserve the existing owner. See [initial setup](../../docs/FEATURES.md#initial-setup) and the [CLI setup guide](../../packages/entr-ble-cli/README.md#2-choose-how-to-access-the-lock) for the available paths.
68
+
69
+ Credentials grant access to the lock. The library leaves persistence to the caller; protect keys and keep them out of logs and version control.
70
+
71
+ ## API guide
72
+
73
+ Every public method and helper has a docstring describing inputs, results, and restrictions. Start with `help(EntrLockClient)` or `help(EntrLockClient.unlock)`. Command docstrings include protocol names and decimal command numbers, matching [const.py](const.py).
74
+
75
+ | Area | Implementation |
76
+ | --- | --- |
77
+ | Pairing, owner enrollment, key activation, session recovery | [client/pairing.py](client/pairing.py) |
78
+ | Lock and unlock | [client/access.py](client/access.py) |
79
+ | Configuration, battery, serial number, device info | [client/status.py](client/status.py) |
80
+ | User keys and permissions | [client/users.py](client/users.py) |
81
+ | Settings, calibration, factory reset | [client/config.py](client/config.py) |
82
+ | Clock, audit trail, error log | [client/diagnostics.py](client/diagnostics.py) |
83
+ | Field encoding and returned dictionary definitions | [client/fields.py](client/fields.py) |
84
+ | Identify locks from BLE advertisements without connecting | [advertising.py](advertising.py) |
85
+
86
+ See [feature coverage](../../docs/FEATURES.md) for firmware restrictions. Reported status may be stale after manual operation; see [protocol notes](../../docs/PROTOCOL.md).
87
+
88
+ ## Errors and timeouts
89
+
90
+ | Exception | Meaning |
91
+ | --- | --- |
92
+ | `ValueError` | Invalid field length or encoding |
93
+ | `EntrLockError` | Lock rejection; exposes numeric `category` and `detail` |
94
+ | `EntrProtocolError` | Malformed/unexpected response or missing session prerequisite; parent of `EntrLockError` |
95
+ | `TimeoutError` | No protocol response within eight seconds |
96
+ | Bleak backend exceptions | Bluetooth connection or I/O failure |
97
+
98
+ The constructor's `timeout` controls the Bleak **connection timeout**, separately from the protocol response timeout. Backend errors propagate to the caller.
99
+
100
+ ## Development
101
+
102
+ From the repository root, run the library tests without Bluetooth hardware:
103
+
104
+ ```sh
105
+ uv run --no-sync python -m unittest discover -s tests -p 'test_library*.py' -v
106
+ ```
107
+
108
+ See the [development guide](../../README.md#development) for workspace setup, integration tests, and packaging.
109
+
110
+ Licensed under the [MIT License](../../LICENSE).
@@ -0,0 +1,5 @@
1
+ """Asynchronous ENTR lock access through EntrLockClient and its protocol errors."""
2
+
3
+ from .client import EntrLockClient, EntrLockError, EntrProtocolError
4
+
5
+ __all__ = ["EntrLockClient", "EntrLockError", "EntrProtocolError"]
@@ -0,0 +1,77 @@
1
+ """Decoding of the ENTR BLE advertisement.
2
+
3
+ Locks broadcast their name and initialisation state in the clear, so a scan can identify
4
+ them without connecting or authenticating.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+
9
+ from . import const
10
+
11
+
12
+ @dataclass
13
+ class LockAdvertisement:
14
+ """A discovered lock's BLE address, decoded name, raw state/version nibbles, and
15
+ RSSI in dBm.
16
+ """
17
+
18
+ address: str
19
+ name: str
20
+ lock_state: int
21
+ advert_version: int
22
+ rssi: int
23
+
24
+ @property
25
+ def state_name(self) -> str:
26
+ """Return the known state label, or ``unknown (<value>)`` for an unrecognized
27
+ state.
28
+ """
29
+ return const.LOCK_STATE_NAMES.get(
30
+ self.lock_state, f"unknown ({self.lock_state})"
31
+ )
32
+
33
+
34
+ # The name sits in a fixed-width field: space padded, and followed by a trailing
35
+ # control byte that belongs to neither the name nor the padding.
36
+ PADDING = bytes(range(0x21))
37
+
38
+
39
+ def parse_advertisement(address: str, adv) -> LockAdvertisement | None:
40
+ """Return the first ENTR manufacturer payload as a ``LockAdvertisement``, or
41
+ ``None`` when none matches.
42
+
43
+ ``adv`` is a Bleak advertisement or an object exposing ``manufacturer_data``,
44
+ ``local_name``, and ``rssi``. Empty payloads and product IDs without a following
45
+ state/version byte are skipped. Version 1 supplies an encoded name when present;
46
+ other layouts use the stripped local name, falling back to ``?``. Name decoding
47
+ replaces invalid characters. ``address`` is copied unchanged.
48
+ """
49
+ for payload in adv.manufacturer_data.values():
50
+ if not payload or payload[0] != const.MANUFACTURER_PRODUCT_ID:
51
+ continue
52
+ custom = payload[1:]
53
+ if not custom:
54
+ continue
55
+ advert_version = (custom[0] >> 4) & 0x0F
56
+ lock_state = custom[0] & 0x0F
57
+ if advert_version == 1 and len(custom) > 4:
58
+ name = _decode_name(custom[3], custom[4:])
59
+ else:
60
+ name = (adv.local_name or "").strip() or "?"
61
+ return LockAdvertisement(
62
+ address=address,
63
+ name=name,
64
+ lock_state=lock_state,
65
+ advert_version=advert_version,
66
+ rssi=adv.rssi,
67
+ )
68
+ return None
69
+
70
+
71
+ def _decode_name(encoding_byte: int, raw: bytes) -> str:
72
+ # Use latin-1 for unknown encoding signs so every byte remains decodable.
73
+ encoding = const.LOCK_NAME_ENCODINGS.get(encoding_byte, "latin-1")
74
+ try:
75
+ return raw.strip(PADDING).decode(encoding, errors="replace")
76
+ except LookupError:
77
+ return raw.strip(PADDING).decode("latin-1", errors="replace")
@@ -0,0 +1,30 @@
1
+ from .access import Access
2
+ from .config import Config
3
+ from .diagnostics import Diagnostics
4
+ from .fields import build_lock_name, settings_status_byte, user_id_bytes
5
+ from .pairing import Pairing
6
+ from .status import Status
7
+ from .transport import EntrLockError, EntrProtocolError
8
+ from .users import Users
9
+
10
+
11
+ class EntrLockClient(Pairing, Access, Status, Users, Config, Diagnostics):
12
+ """Asynchronous ENTR lock client combining provisioning, access, status, users,
13
+ configuration and diagnostics.
14
+
15
+ Accepts a Bluetooth address or Bleak BLEDevice and an optional connection timeout in
16
+ seconds. Call connect(), then pair() and handshake() for provisioning or
17
+ kdf_resync() with saved credentials for normal use. Run one command at a time and
18
+ call disconnect() in a finally block. See TransportClient for shared session state
19
+ and command errors.
20
+ """
21
+
22
+
23
+ __all__ = [
24
+ "EntrLockClient",
25
+ "EntrLockError",
26
+ "EntrProtocolError",
27
+ "build_lock_name",
28
+ "settings_status_byte",
29
+ "user_id_bytes",
30
+ ]
@@ -0,0 +1,58 @@
1
+ import entr_ble.const as const
2
+
3
+ from .fields import fixed_bytes
4
+ from .transport import EntrProtocolError, TransportClient
5
+
6
+
7
+ class Access(TransportClient):
8
+ """Credential-based physical lock operations over an established session."""
9
+
10
+ async def unlock(
11
+ self, user_id: bytes, app_id: bytes, ble_ekey: bytes, mode: int | None = None
12
+ ) -> None:
13
+ """UNLOCK (17): unlock using a 16-byte user ID, 16-byte application ID and
14
+ 32-byte BLE key.
15
+
16
+ Requires an established session and an active credential. Mode 0 selects
17
+ auto-lock and mode 1 manual operation; omitted mode follows cached status,
18
+ falling back to manual when status is unavailable. Returns None after an
19
+ operation-status response or explicit success; a missing confirmation raises
20
+ EntrProtocolError and lock rejection raises EntrLockError. Invalid field lengths
21
+ raise ValueError.
22
+ """
23
+ if mode is None:
24
+ # Derive the mode from auto-lock: enabled sends 0, disabled sends 1.
25
+ auto_lock = self.status["auto_lock"] if self.status else False
26
+ mode = const.MODE_AUTO if auto_lock else const.MODE_MANUAL
27
+ fields = (
28
+ fixed_bytes(user_id, 16, "user id")
29
+ + fixed_bytes(app_id, 16, "application id")
30
+ + fixed_bytes(ble_ekey, 32, "lock key")
31
+ + bytes([mode])
32
+ )
33
+ await self._operate(const.CMD_UNLOCK, fields)
34
+
35
+ async def lock(self, user_id: bytes, app_id: bytes, ble_ekey: bytes) -> None:
36
+ """LOCK (18): lock using a 16-byte user ID, 16-byte application ID and 32-byte
37
+ BLE key.
38
+
39
+ Requires an established session and an active credential; the operation always
40
+ sends mode 0. Returns None after an operation-status response or explicit
41
+ success; a missing confirmation raises EntrProtocolError and lock rejection
42
+ raises EntrLockError. Invalid field lengths raise ValueError.
43
+ """
44
+ # Lock always uses mode 0.
45
+ fields = (
46
+ fixed_bytes(user_id, 16, "user id")
47
+ + fixed_bytes(app_id, 16, "application id")
48
+ + fixed_bytes(ble_ekey, 32, "lock key")
49
+ + bytes([const.MODE_AUTO])
50
+ )
51
+ await self._operate(const.CMD_LOCK, fields)
52
+
53
+ async def _operate(self, command, fields):
54
+ outer_command, payload = await self._exchange_encrypted(command, fields)
55
+ if outer_command != const.CMD_OP_STATUS and not self._is_explicit_success(
56
+ outer_command, payload, command
57
+ ):
58
+ raise EntrProtocolError(f"lock did not confirm command {command}")