entr-ble-cli 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,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,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: entr-ble-cli
3
+ Version: 1.0.0
4
+ Summary: Command-line interface for ENTR Bluetooth locks
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Dist: entr-ble==1.0.0
8
+ Requires-Python: >=3.14
9
+ Project-URL: Repository, https://github.com/johannlejeune/entr-ble
10
+ Project-URL: Issues, https://github.com/johannlejeune/entr-ble/issues
11
+ Description-Content-Type: text/markdown
12
+
13
+ # ENTR BLE · CLI
14
+
15
+ **Set up and control your ENTR lock from the terminal.** Discover locks, manage user keys, change settings, and export credentials to Home Assistant.
16
+
17
+ [Project overview](../../README.md) · [Python library](../../src/entr_ble/README.md) · [Home Assistant](../../custom_components/entr_ble/README.md) · [Feature coverage](../../docs/FEATURES.md)
18
+
19
+ ## Install
20
+
21
+ Requires **Python 3.14+** and a working Bluetooth adapter. On Linux, Bluetooth access requires BlueZ and access to the system D-Bus.
22
+
23
+ Run the CLI without a checkout:
24
+
25
+ ```sh
26
+ uvx entr-ble-cli --help
27
+ uvx entr-ble-cli scan
28
+ ```
29
+
30
+ For a persistent command, use `uv tool install entr-ble-cli`, then run `entr-ble-cli` directly. With `uvx`, prefix the commands below with `uvx`.
31
+
32
+ Alternatively, from a checkout, run:
33
+
34
+ ```sh
35
+ uv sync --locked --package entr-ble-cli
36
+ uv run --package entr-ble-cli entr-ble-cli --help
37
+ ```
38
+
39
+ For the checkout workflow, prefix the commands below with `uv run --package entr-ble-cli`. The `entr-ble-cli` package supplies the `entr-ble-cli` command; installing the `entr-ble` library alone does not.
40
+
41
+ ## Quick start
42
+
43
+ ### 1. Find your lock
44
+
45
+ ```sh
46
+ entr-ble-cli scan
47
+ ```
48
+
49
+ The scan lists each lock's address, advertised name, initialization state, and signal strength. Replace `ADDRESS` in the examples below with the exact address shown by your scan.
50
+
51
+ For a longer scan, or to include other BLE devices:
52
+
53
+ ```sh
54
+ entr-ble-cli scan --timeout 10
55
+ entr-ble-cli scan --all
56
+ ```
57
+
58
+ ### 2. Choose how to access the lock
59
+
60
+ Run **one** of the following setup commands. Each saves the resulting credential locally for subsequent commands.
61
+
62
+ | Your situation | Command | Effect |
63
+ | --- | --- | --- |
64
+ | New, uninitialized lock | `entr-ble-cli set-owner ADDRESS --name "Front door"` | Creates the first owner and asks for a new password |
65
+ | Initialized lock; you want owner access | `entr-ble-cli enroll ADDRESS --name "Front door"` | Asks for the current owner password and replaces the owner credential |
66
+ | You have an activation code from the owner | `entr-ble-cli activate ADDRESS KEY_CODE` | Redeems an additional key while preserving the owner |
67
+
68
+ > [!WARNING]
69
+ > `enroll` replaces the lock's single owner slot. The previous owner, including the mobile app if it holds that slot, loses access. Use `activate` to keep the existing owner.
70
+
71
+ `KEY_CODE` is the six-character code supplied by the owner. A setup command refuses to overwrite credentials already saved for that address.
72
+
73
+ For a new lock, `--name` accepts up to 12 bytes. `--user` sets the owner name, and `--provider` selects the brand id; see `entr-ble-cli set-owner --help`. After initial setup, an uncalibrated lock needs `calibrate` followed by `magnet-calibrate` with the door magnet in place.
74
+
75
+ ### 3. Read status and control the lock
76
+
77
+ ```sh
78
+ entr-ble-cli status ADDRESS
79
+ entr-ble-cli unlock ADDRESS
80
+ entr-ble-cli lock ADDRESS
81
+ ```
82
+
83
+ Commands restore your saved session and close the Bluetooth connection when finished. Unlock behavior follows the lock's auto-lock setting. Reported state can be stale after manual operation; see [protocol limitations](../../docs/PROTOCOL.md).
84
+
85
+ ## Everyday tasks
86
+
87
+ ### Share access
88
+
89
+ With an owner or admin key, create a user:
90
+
91
+ ```sh
92
+ entr-ble-cli create-user ADDRESS Guest
93
+ ```
94
+
95
+ The command asks for the admin password and prints an activation code and the command to redeem it. Run that activation command on the recipient's machine with its own credential store.
96
+
97
+ The code stays redeemable for **three hours by default**. `--expiration` changes that window; the activated key does not expire when the code's redemption window ends.
98
+
99
+ ```sh
100
+ entr-ble-cli list-users ADDRESS
101
+ entr-ble-cli disable-user ADDRESS Guest
102
+ entr-ble-cli enable-user ADDRESS Guest
103
+ entr-ble-cli delete-user ADDRESS Guest
104
+ ```
105
+
106
+ Disable suspends a key; enable restores it; delete permanently revokes it. `create-user --role admin` creates an admin key. After activation, that admin uses `set-admin-code` to set its personal password. Accessory roles are also available; radio pairing happens on the hardware, as described in [feature coverage](../../docs/FEATURES.md#users-and-accessories).
107
+
108
+ ### Change settings
109
+
110
+ ```sh
111
+ entr-ble-cli settings ADDRESS --volume medium --auto-lock on
112
+ entr-ble-cli change-admin-code ADDRESS
113
+ ```
114
+
115
+ Volume choices are `high`, `medium`, `low`, and `muted`. These commands require an owner or admin key. The CLI reads the current configuration before applying changes. If the lock name was not saved during enrollment, pass `--name "Front door"` once; the CLI saves it for later settings commands.
116
+
117
+ ### Bring an existing key into Home Assistant
118
+
119
+ ```sh
120
+ entr-ble-cli export-homeassistant ADDRESS
121
+ ```
122
+
123
+ In the [integration setup](../../custom_components/entr_ble/README.md#use-credentials-from-the-cli), choose **Import existing credentials**, enter the same address, and paste the output into **Credentials JSON**.
124
+
125
+ Export reads the local store without connecting to the lock or changing credentials. Home Assistant stores its own copy. The JSON contains access keys: keep it private.
126
+
127
+ ## Command reference
128
+
129
+ Run `entr-ble-cli` to see grouped help, or `entr-ble-cli COMMAND --help` for arguments and options. All lock commands below take `ADDRESS`; `scan` does not.
130
+
131
+ | Group | Commands | Purpose |
132
+ | --- | --- | --- |
133
+ | Discovery and setup | `scan`, `set-owner`, `enroll`, `activate` | Find locks and save an access credential |
134
+ | Lock control | `lock`, `unlock` | Operate the lock |
135
+ | Status and information | `status`, `info`, `device-info` | Read status, serial number, model, and firmware |
136
+ | Users and keys | `list-users`, `create-user`, `set-admin-code`, `disable-user`, `enable-user`, `delete-user` | Manage access |
137
+ | Settings | `settings`, `change-admin-code` | Change volume, auto-lock, name, or admin password |
138
+ | Maintenance | `calibrate`, `magnet-calibrate`, `factory-reset` | Calibrate hardware or restore factory settings |
139
+ | Clock and logs | `set-time`, `audit-trail`, `get-errors` | Set UTC time and read diagnostics |
140
+ | Home Assistant | `export-homeassistant` | Export one saved credential as JSON |
141
+
142
+ `set-time` and `audit-trail` require NIZ firmware. `device-info` requires the FOTA GATT service and communication version 1.29r3+; `get-errors` requires the FOTA service. See [feature coverage](../../docs/FEATURES.md) for the full compatibility matrix.
143
+
144
+ > [!WARNING]
145
+ > `factory-reset` erases lock users and settings, then removes the local credential after success. It asks you to type `yes`; `--yes` skips that confirmation.
146
+
147
+ ## Passwords and scripting
148
+
149
+ Commands needing a password ask for it **without echo**, before connecting. `set-owner` and `set-admin-code` ask for the password to set; `change-admin-code` asks for both current and new passwords. At the `audit-trail` prompt, press Enter to use the factory audit password.
150
+
151
+ For scripts without a terminal, use `-p PASSWORD` or `--password PASSWORD`. For password changes, also supply `--new-password NEW`. Command-line passwords may appear in shell history and process arguments; prefer the hidden prompt when working interactively.
152
+
153
+ Results go to **stdout**, progress to **stderr**. Add `-q` or `--quiet` before or after a command to suppress progress while keeping warnings and errors:
154
+
155
+ ```sh
156
+ entr-ble-cli --quiet status ADDRESS > status.txt
157
+ entr-ble-cli status ADDRESS --quiet
158
+ ```
159
+
160
+ Expected Bluetooth and lock failures produce a short error and exit with code `1`; interrupted input exits with `130`. Unexpected internal errors retain their traceback. Ordinary command output is human-readable; `export-homeassistant` produces JSON.
161
+
162
+ ## Credential storage
163
+
164
+ | Setting | Value |
165
+ | --- | --- |
166
+ | Default store | `~/.config/entr-ble/credentials.json` |
167
+ | Override | `ENTR_BLE_STORE` environment variable |
168
+ | Protection | Access restricted to the current user; unencrypted at rest |
169
+ | Writes | Atomic file replacement; no coordination between concurrent writers |
170
+
171
+ Keep the store private, backed up securely, and out of version control. Use one process at a time when modifying a shared store. An alternate store can keep credentials for a separate client:
172
+
173
+ ```sh
174
+ export ENTR_BLE_STORE="$HOME/.config/entr-ble/guest.json"
175
+ entr-ble-cli activate ADDRESS KEY_CODE
176
+ ```
177
+
178
+ Subsequent commands in that shell use the alternate store.
179
+
180
+ ## Troubleshooting
181
+
182
+ | Symptom | Next step |
183
+ | --- | --- |
184
+ | No lock found or communication times out | Move closer, check Bluetooth is enabled, and try `scan --timeout 10 --all` |
185
+ | Bluetooth access denied | Check application Bluetooth permissions; on Linux, check BlueZ and system D-Bus access |
186
+ | No saved key | Check the address and `ENTR_BLE_STORE`; choose the appropriate setup method above |
187
+ | Lock refuses a command | Check the password, key permissions, and firmware support |
188
+ | Settings command needs a name | Pass `--name` once to save the lock name locally |
189
+ | Status disagrees with manual operation | Read the [firmware state limitations](../../docs/PROTOCOL.md); a reported value can be stale |
190
+
191
+ For a bug report, include the command, error, software version, and lock model/firmware. Remove passwords and credential material before sharing output.
192
+
193
+ ## Development
194
+
195
+ From the repository root, after the checkout installation above:
196
+
197
+ ```sh
198
+ uv run --package entr-ble-cli python -m unittest discover -s packages/entr-ble-cli/tests -v
199
+ ```
200
+
201
+ Tests use simulated BLE responses and do not operate a lock. See the [workspace development guide](../../README.md#development) for all checks.
202
+
203
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,191 @@
1
+ # ENTR BLE · CLI
2
+
3
+ **Set up and control your ENTR lock from the terminal.** Discover locks, manage user keys, change settings, and export credentials to Home Assistant.
4
+
5
+ [Project overview](../../README.md) · [Python library](../../src/entr_ble/README.md) · [Home Assistant](../../custom_components/entr_ble/README.md) · [Feature coverage](../../docs/FEATURES.md)
6
+
7
+ ## Install
8
+
9
+ Requires **Python 3.14+** and a working Bluetooth adapter. On Linux, Bluetooth access requires BlueZ and access to the system D-Bus.
10
+
11
+ Run the CLI without a checkout:
12
+
13
+ ```sh
14
+ uvx entr-ble-cli --help
15
+ uvx entr-ble-cli scan
16
+ ```
17
+
18
+ For a persistent command, use `uv tool install entr-ble-cli`, then run `entr-ble-cli` directly. With `uvx`, prefix the commands below with `uvx`.
19
+
20
+ Alternatively, from a checkout, run:
21
+
22
+ ```sh
23
+ uv sync --locked --package entr-ble-cli
24
+ uv run --package entr-ble-cli entr-ble-cli --help
25
+ ```
26
+
27
+ For the checkout workflow, prefix the commands below with `uv run --package entr-ble-cli`. The `entr-ble-cli` package supplies the `entr-ble-cli` command; installing the `entr-ble` library alone does not.
28
+
29
+ ## Quick start
30
+
31
+ ### 1. Find your lock
32
+
33
+ ```sh
34
+ entr-ble-cli scan
35
+ ```
36
+
37
+ The scan lists each lock's address, advertised name, initialization state, and signal strength. Replace `ADDRESS` in the examples below with the exact address shown by your scan.
38
+
39
+ For a longer scan, or to include other BLE devices:
40
+
41
+ ```sh
42
+ entr-ble-cli scan --timeout 10
43
+ entr-ble-cli scan --all
44
+ ```
45
+
46
+ ### 2. Choose how to access the lock
47
+
48
+ Run **one** of the following setup commands. Each saves the resulting credential locally for subsequent commands.
49
+
50
+ | Your situation | Command | Effect |
51
+ | --- | --- | --- |
52
+ | New, uninitialized lock | `entr-ble-cli set-owner ADDRESS --name "Front door"` | Creates the first owner and asks for a new password |
53
+ | Initialized lock; you want owner access | `entr-ble-cli enroll ADDRESS --name "Front door"` | Asks for the current owner password and replaces the owner credential |
54
+ | You have an activation code from the owner | `entr-ble-cli activate ADDRESS KEY_CODE` | Redeems an additional key while preserving the owner |
55
+
56
+ > [!WARNING]
57
+ > `enroll` replaces the lock's single owner slot. The previous owner, including the mobile app if it holds that slot, loses access. Use `activate` to keep the existing owner.
58
+
59
+ `KEY_CODE` is the six-character code supplied by the owner. A setup command refuses to overwrite credentials already saved for that address.
60
+
61
+ For a new lock, `--name` accepts up to 12 bytes. `--user` sets the owner name, and `--provider` selects the brand id; see `entr-ble-cli set-owner --help`. After initial setup, an uncalibrated lock needs `calibrate` followed by `magnet-calibrate` with the door magnet in place.
62
+
63
+ ### 3. Read status and control the lock
64
+
65
+ ```sh
66
+ entr-ble-cli status ADDRESS
67
+ entr-ble-cli unlock ADDRESS
68
+ entr-ble-cli lock ADDRESS
69
+ ```
70
+
71
+ Commands restore your saved session and close the Bluetooth connection when finished. Unlock behavior follows the lock's auto-lock setting. Reported state can be stale after manual operation; see [protocol limitations](../../docs/PROTOCOL.md).
72
+
73
+ ## Everyday tasks
74
+
75
+ ### Share access
76
+
77
+ With an owner or admin key, create a user:
78
+
79
+ ```sh
80
+ entr-ble-cli create-user ADDRESS Guest
81
+ ```
82
+
83
+ The command asks for the admin password and prints an activation code and the command to redeem it. Run that activation command on the recipient's machine with its own credential store.
84
+
85
+ The code stays redeemable for **three hours by default**. `--expiration` changes that window; the activated key does not expire when the code's redemption window ends.
86
+
87
+ ```sh
88
+ entr-ble-cli list-users ADDRESS
89
+ entr-ble-cli disable-user ADDRESS Guest
90
+ entr-ble-cli enable-user ADDRESS Guest
91
+ entr-ble-cli delete-user ADDRESS Guest
92
+ ```
93
+
94
+ Disable suspends a key; enable restores it; delete permanently revokes it. `create-user --role admin` creates an admin key. After activation, that admin uses `set-admin-code` to set its personal password. Accessory roles are also available; radio pairing happens on the hardware, as described in [feature coverage](../../docs/FEATURES.md#users-and-accessories).
95
+
96
+ ### Change settings
97
+
98
+ ```sh
99
+ entr-ble-cli settings ADDRESS --volume medium --auto-lock on
100
+ entr-ble-cli change-admin-code ADDRESS
101
+ ```
102
+
103
+ Volume choices are `high`, `medium`, `low`, and `muted`. These commands require an owner or admin key. The CLI reads the current configuration before applying changes. If the lock name was not saved during enrollment, pass `--name "Front door"` once; the CLI saves it for later settings commands.
104
+
105
+ ### Bring an existing key into Home Assistant
106
+
107
+ ```sh
108
+ entr-ble-cli export-homeassistant ADDRESS
109
+ ```
110
+
111
+ In the [integration setup](../../custom_components/entr_ble/README.md#use-credentials-from-the-cli), choose **Import existing credentials**, enter the same address, and paste the output into **Credentials JSON**.
112
+
113
+ Export reads the local store without connecting to the lock or changing credentials. Home Assistant stores its own copy. The JSON contains access keys: keep it private.
114
+
115
+ ## Command reference
116
+
117
+ Run `entr-ble-cli` to see grouped help, or `entr-ble-cli COMMAND --help` for arguments and options. All lock commands below take `ADDRESS`; `scan` does not.
118
+
119
+ | Group | Commands | Purpose |
120
+ | --- | --- | --- |
121
+ | Discovery and setup | `scan`, `set-owner`, `enroll`, `activate` | Find locks and save an access credential |
122
+ | Lock control | `lock`, `unlock` | Operate the lock |
123
+ | Status and information | `status`, `info`, `device-info` | Read status, serial number, model, and firmware |
124
+ | Users and keys | `list-users`, `create-user`, `set-admin-code`, `disable-user`, `enable-user`, `delete-user` | Manage access |
125
+ | Settings | `settings`, `change-admin-code` | Change volume, auto-lock, name, or admin password |
126
+ | Maintenance | `calibrate`, `magnet-calibrate`, `factory-reset` | Calibrate hardware or restore factory settings |
127
+ | Clock and logs | `set-time`, `audit-trail`, `get-errors` | Set UTC time and read diagnostics |
128
+ | Home Assistant | `export-homeassistant` | Export one saved credential as JSON |
129
+
130
+ `set-time` and `audit-trail` require NIZ firmware. `device-info` requires the FOTA GATT service and communication version 1.29r3+; `get-errors` requires the FOTA service. See [feature coverage](../../docs/FEATURES.md) for the full compatibility matrix.
131
+
132
+ > [!WARNING]
133
+ > `factory-reset` erases lock users and settings, then removes the local credential after success. It asks you to type `yes`; `--yes` skips that confirmation.
134
+
135
+ ## Passwords and scripting
136
+
137
+ Commands needing a password ask for it **without echo**, before connecting. `set-owner` and `set-admin-code` ask for the password to set; `change-admin-code` asks for both current and new passwords. At the `audit-trail` prompt, press Enter to use the factory audit password.
138
+
139
+ For scripts without a terminal, use `-p PASSWORD` or `--password PASSWORD`. For password changes, also supply `--new-password NEW`. Command-line passwords may appear in shell history and process arguments; prefer the hidden prompt when working interactively.
140
+
141
+ Results go to **stdout**, progress to **stderr**. Add `-q` or `--quiet` before or after a command to suppress progress while keeping warnings and errors:
142
+
143
+ ```sh
144
+ entr-ble-cli --quiet status ADDRESS > status.txt
145
+ entr-ble-cli status ADDRESS --quiet
146
+ ```
147
+
148
+ Expected Bluetooth and lock failures produce a short error and exit with code `1`; interrupted input exits with `130`. Unexpected internal errors retain their traceback. Ordinary command output is human-readable; `export-homeassistant` produces JSON.
149
+
150
+ ## Credential storage
151
+
152
+ | Setting | Value |
153
+ | --- | --- |
154
+ | Default store | `~/.config/entr-ble/credentials.json` |
155
+ | Override | `ENTR_BLE_STORE` environment variable |
156
+ | Protection | Access restricted to the current user; unencrypted at rest |
157
+ | Writes | Atomic file replacement; no coordination between concurrent writers |
158
+
159
+ Keep the store private, backed up securely, and out of version control. Use one process at a time when modifying a shared store. An alternate store can keep credentials for a separate client:
160
+
161
+ ```sh
162
+ export ENTR_BLE_STORE="$HOME/.config/entr-ble/guest.json"
163
+ entr-ble-cli activate ADDRESS KEY_CODE
164
+ ```
165
+
166
+ Subsequent commands in that shell use the alternate store.
167
+
168
+ ## Troubleshooting
169
+
170
+ | Symptom | Next step |
171
+ | --- | --- |
172
+ | No lock found or communication times out | Move closer, check Bluetooth is enabled, and try `scan --timeout 10 --all` |
173
+ | Bluetooth access denied | Check application Bluetooth permissions; on Linux, check BlueZ and system D-Bus access |
174
+ | No saved key | Check the address and `ENTR_BLE_STORE`; choose the appropriate setup method above |
175
+ | Lock refuses a command | Check the password, key permissions, and firmware support |
176
+ | Settings command needs a name | Pass `--name` once to save the lock name locally |
177
+ | Status disagrees with manual operation | Read the [firmware state limitations](../../docs/PROTOCOL.md); a reported value can be stale |
178
+
179
+ For a bug report, include the command, error, software version, and lock model/firmware. Remove passwords and credential material before sharing output.
180
+
181
+ ## Development
182
+
183
+ From the repository root, after the checkout installation above:
184
+
185
+ ```sh
186
+ uv run --package entr-ble-cli python -m unittest discover -s packages/entr-ble-cli/tests -v
187
+ ```
188
+
189
+ Tests use simulated BLE responses and do not operate a lock. See the [workspace development guide](../../README.md#development) for all checks.
190
+
191
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "entr-ble-cli"
3
+ version = "1.0.0"
4
+ description = "Command-line interface for ENTR Bluetooth locks"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14"
9
+ dependencies = ["entr-ble==1.0.0"]
10
+
11
+ [project.urls]
12
+ Repository = "https://github.com/johannlejeune/entr-ble"
13
+ Issues = "https://github.com/johannlejeune/entr-ble/issues"
14
+
15
+ [project.scripts]
16
+ entr-ble-cli = "entr_ble_cli:main"
17
+
18
+ [tool.uv.sources.entr-ble]
19
+ workspace = true
20
+
21
+ [build-system]
22
+ requires = ["uv_build>=0.12.0,<0.13.0"]
23
+ build-backend = "uv_build"
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "entr-ble-cli"
3
+ version = "1.0.0"
4
+ description = "Command-line interface for ENTR Bluetooth locks"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14"
9
+ dependencies = [
10
+ "entr-ble==1.0.0",
11
+ ]
12
+
13
+ [project.urls]
14
+ Repository = "https://github.com/johannlejeune/entr-ble"
15
+ Issues = "https://github.com/johannlejeune/entr-ble/issues"
16
+
17
+ [project.scripts]
18
+ entr-ble-cli = "entr_ble_cli:main"
19
+
20
+ [tool.uv.sources]
21
+ entr-ble = { workspace = true }
22
+
23
+ [build-system]
24
+ requires = ["uv_build>=0.12.0,<0.13.0"]
25
+ build-backend = "uv_build"
@@ -0,0 +1,7 @@
1
+ def main() -> None:
2
+ from .cli import main as cli_main
3
+
4
+ cli_main()
5
+
6
+
7
+ __all__ = ["main"]
@@ -0,0 +1,132 @@
1
+ import argparse
2
+ import asyncio
3
+ import logging
4
+
5
+ from bleak.exc import (
6
+ BleakBluetoothNotAvailableError,
7
+ BleakBluetoothNotAvailableReason,
8
+ BleakDeviceNotFoundError,
9
+ BleakError,
10
+ )
11
+
12
+ from entr_ble import EntrLockError, EntrProtocolError
13
+
14
+ from .commands import (
15
+ access,
16
+ config,
17
+ discovery,
18
+ maintenance,
19
+ settings,
20
+ setup,
21
+ status,
22
+ users,
23
+ )
24
+ from .commands.common import handle
25
+ from .shared import CommandError
26
+
27
+
28
+ class HelpFormatter(argparse.RawDescriptionHelpFormatter):
29
+ def __init__(self, prog, groups):
30
+ super().__init__(prog, max_help_position=32)
31
+ self.groups = groups
32
+
33
+ def _format_action(self, action):
34
+ if not isinstance(action, argparse._SubParsersAction):
35
+ return super()._format_action(action)
36
+ commands = {command.dest: command for command in action._get_subactions()}
37
+ parts = []
38
+ for title, names in self.groups:
39
+ parts.append(f"{' ' * self._current_indent}{title}:\n")
40
+ self._indent()
41
+ for name in names:
42
+ parts.append(super()._format_action(commands[name]))
43
+ self._dedent()
44
+ parts.append("\n")
45
+ return "".join(parts)
46
+
47
+
48
+ def main() -> None:
49
+ groups = []
50
+ parser = argparse.ArgumentParser(
51
+ prog="entr-ble-cli",
52
+ description="Set up and control an ENTR Bluetooth lock.",
53
+ usage="%(prog)s [options] COMMAND ...",
54
+ formatter_class=lambda prog: HelpFormatter(prog, groups),
55
+ epilog=(
56
+ "Getting started:\n"
57
+ " 1. Find your lock: entr-ble-cli scan\n"
58
+ " 2. Set up access with set-owner (new lock), enroll (replace the owner),\n"
59
+ " or activate (use a key supplied by the owner).\n"
60
+ " 3. Read its status: entr-ble-cli status ADDRESS\n\n"
61
+ "Replace ADDRESS with the Bluetooth address printed by scan.\n"
62
+ "For a command's arguments and options, run:\n"
63
+ " entr-ble-cli COMMAND --help\n\n"
64
+ "Example: entr-ble-cli unlock AA:BB:CC:DD:EE:FF"
65
+ ),
66
+ )
67
+ parser.add_argument(
68
+ "-q", "--quiet", action="store_true", help="hide progress messages"
69
+ )
70
+ sub = parser.add_subparsers(dest="command", title="commands", metavar="COMMAND")
71
+ for title, modules in (
72
+ ("Discovery and setup", (discovery, setup)),
73
+ ("Lock control", (access,)),
74
+ ("Status and information", (status,)),
75
+ ("Users and keys", (users,)),
76
+ ("Settings", (settings,)),
77
+ ("Maintenance and logs", (maintenance,)),
78
+ ("Home Assistant", (config,)),
79
+ ):
80
+ existing = set(sub.choices)
81
+ for module in modules:
82
+ module.register(sub)
83
+ groups.append((title, [name for name in sub.choices if name not in existing]))
84
+ for command in sub.choices.values():
85
+ command.add_argument(
86
+ "-q",
87
+ "--quiet",
88
+ action="store_true",
89
+ default=argparse.SUPPRESS,
90
+ help="hide progress messages",
91
+ )
92
+ args = parser.parse_args()
93
+ if args.command is None:
94
+ parser.print_help()
95
+ return
96
+ logger = logging.getLogger("entr_ble_cli")
97
+ logger.handlers = [logging.StreamHandler()]
98
+ logger.setLevel(logging.WARNING if args.quiet else logging.INFO)
99
+ logger.propagate = False
100
+ try:
101
+ if args.command == "export-homeassistant":
102
+ config.run(args)
103
+ else:
104
+ asyncio.run(handle(args))
105
+ except (CommandError, EntrProtocolError, BleakError, OSError, ValueError) as exc:
106
+ parser.exit(1, f"Error: {error_message(exc)}\n")
107
+ except KeyboardInterrupt, EOFError:
108
+ parser.exit(130, "Aborted.\n")
109
+
110
+
111
+ def error_message(exc):
112
+ if isinstance(exc, BleakBluetoothNotAvailableError):
113
+ if exc.reason == BleakBluetoothNotAvailableReason.POWERED_OFF:
114
+ return "Bluetooth is turned off. Turn it on and try again."
115
+ if exc.reason.name.startswith("DENIED_"):
116
+ return "Bluetooth access was denied. Allow this application to use Bluetooth and try again."
117
+ return "Bluetooth is unavailable. Check that a Bluetooth adapter is connected and enabled."
118
+ if isinstance(exc, BleakDeviceNotFoundError):
119
+ return "The lock could not be found. Move closer and run 'entr-ble-cli scan' to check its address."
120
+ if isinstance(exc, TimeoutError):
121
+ return "Bluetooth communication timed out. Move closer to the lock and check that Bluetooth is enabled."
122
+ if isinstance(exc, BleakError):
123
+ return "Bluetooth communication failed. Check that Bluetooth is enabled and the lock is nearby, then try again."
124
+ if isinstance(exc, EntrLockError):
125
+ return "The lock refused the command. Check your password, key permissions, and whether your lock supports this command."
126
+ if isinstance(exc, EntrProtocolError):
127
+ return "The lock sent an unexpected response. Check the connection and whether your lock supports this command."
128
+ if isinstance(exc, OSError):
129
+ if exc.filename:
130
+ return f"Could not access {exc.filename}: {exc.strerror or 'check the file and its permissions'}."
131
+ return "Could not communicate over Bluetooth. Check your adapter and Bluetooth service, then try again."
132
+ return str(exc)
@@ -0,0 +1,16 @@
1
+ def register(sub):
2
+ p = sub.add_parser("unlock", help="unlock the door")
3
+ p.add_argument("address")
4
+ p.set_defaults(run=run)
5
+
6
+ p = sub.add_parser("lock", help="lock the door")
7
+ p.add_argument("address")
8
+ p.set_defaults(run=run)
9
+
10
+
11
+ async def run(client, creds, args) -> list[str]:
12
+ app_id = bytes.fromhex(creds.app_id)
13
+ await getattr(client, args.command)(
14
+ bytes.fromhex(creds.user_id), app_id, bytes.fromhex(creds.ble_ekey)
15
+ )
16
+ return [f"{args.command} sent"]