ttlock-ble
Async Python SDK for controlling TTLock-family smart locks (TTLock / DLock-XP) over Bluetooth Low Energy — lock, unlock, state and battery, passcode management, on-device operation log, and real-time push events, with no cloud round-trip on every operation.
⚠️ Unofficial. Not affiliated with, endorsed by, or supported by TTLock / Sciener or any lock vendor. It speaks the BLE V3 protocol and the cloud API the official mobile app uses (reverse-engineered). Either side can change and break this SDK without notice.
Status
Beta — used in production by the ha-ttlock-ble
Home Assistant integration. See CHANGELOG.md for changes between releases.
How it works
The lock is driven entirely over BLE, but the per-lock credentials (the "eKey") come from the TTLock cloud once:
TTLockCloud (HTTP, one-time) ──► VirtualKey (aesKey, lockMac, …) ──► TTLockClient (BLE, every operation)
You bootstrap the eKeys from the cloud a single time (caching them locally), then every lock/unlock happens offline over Bluetooth.
Install
pip install ttlock-ble
Or, with uv:
uv add ttlock-ble
The ttlock command-line tool ships as an optional extra:
pip install "ttlock-ble[cli]"
Requires Python 3.14+ and a BLE adapter supported by bleak.
Quick start
1. Bootstrap eKeys from the cloud (once)
import asyncio
from ttlock_ble import TTLockCloud
async def main() -> None:
async with TTLockCloud() as cloud:
await cloud.login("you@example.com", "your-password")
# First time from a new machine the server requires a device check:
# await cloud.request_login_verification_code("you@example.com")
# await cloud.validate_new_device("you@example.com", code_from_email)
keys = await cloud.list_keys()
for k in keys:
print(k.lockAlias, k.lockMac)
asyncio.run(main())
2. Control a lock over BLE
import asyncio
from ttlock_ble import TTLockClient
async def main(virtual_key) -> None:
async with TTLockClient(virtual_key) as lock: # scans + connects + handshake
await lock.unlock()
state, battery = await lock.query_state()
print(state, f"{battery}%")
asyncio.run(main(keys[0]))
TTLockClient is an async context manager: it scans for key.lockMac, picks the GATT
service, runs the CHECK_USER_TIME handshake, then issues commands. Pass a pre-resolved
device= (or use TTLockClient.from_ble_device(...)) to skip the scan — that is how the
Home Assistant integration hands in a BLEDevice from HA's own bluetooth manager.
Real-time events
After a command the link is kept open (keep_alive_after_command, 25 s by default) so
auto-lock, keypad and fingerprint operations stream back as LockEvents:
def on_event(event):
print("lock event:", event)
lock.add_event_listener(on_event)
Passive state, without connecting
The lock also publishes its bolt position and battery level in the manufacturer data of
every BLE advertisement. LockAdvertisement decodes that, so a scanner can follow the
lock without ever opening a session — the only way to observe an auto-lock, which the
firmware writes no log record for and cannot push once it has dropped the link:
from ttlock_ble import LockAdvertisement
for company_id, payload in advertisement_data.manufacturer_data.items():
state = LockAdvertisement.from_manufacturer_data(company_id, payload)
if state is not None and state.lock_mac.lower() == key.lockMac.lower():
print(state.lock_state, state.battery)
It returns None for anything that is not a stateful TTLock advertisement, and never
raises. Compare lock_mac against the address you expected before trusting the result
(case-insensitively — MAC casing varies between sources): a payload long enough to
decode is not proof that it came from a lock.
CLI
Installing the package with the cli extra (pip install "ttlock-ble[cli]") exposes a
ttlock command (env: TTLOCK_EMAIL, TTLOCK_PASSWORD,
optional TTLOCK_KEY_STORE, default ~/.ttlock/keys.json; a .env file is honored):
| Command | What it does |
|---|---|
ttlock sync |
Log in to the cloud and cache the account's eKeys locally |
ttlock verify <code> |
Register this machine with the new-device verification code |
ttlock list |
Show cached locks |
ttlock unlock <lock> |
Unlock a lock over Bluetooth |
ttlock lock <lock> |
Lock a lock over Bluetooth |
ttlock state <lock> |
Query current state and battery |
ttlock battery <lock> |
Show battery percentage |
ttlock sound <lock> <on|off> |
Turn the keypad/lock beep on or off (admin eKey required) |
ttlock device-info <lock> |
Show the standard BLE Device Information Service fields |
ttlock add-passcode <lock> <code> |
Provision a keypad passcode (admin eKey required) |
ttlock delete-passcode <lock> <code> |
Remove a keypad passcode (admin eKey required) |
ttlock clear-passcodes <lock> |
Wipe ALL keypad passcodes — no undo (admin eKey required) |
ttlock get-auto-lock <lock> |
Read the auto-lock delay in seconds (admin eKey required) |
ttlock set-auto-lock <lock> <seconds> |
Set the auto-lock delay in seconds (admin eKey required) |
ttlock get-auto-lock-limits <lock> |
Show the min/max auto-lock delay this lock accepts (admin eKey required) |
Typical first run: ttlock sync → (if prompted) check email → ttlock verify <code> →
ttlock sync again → ttlock unlock <lock>.
API overview
Everything below is re-exported from the top-level ttlock_ble package.
TTLockClient (BLE)
| Method | Purpose |
|---|---|
connect() / disconnect() |
Open / close the BLE link (or use async with) |
unlock() / lock() |
Drive the bolt |
query_state() |
(LockState | None, battery_percent | None) |
get_auto_lock_time() / set_auto_lock_time(seconds) |
Read / set the auto-lock delay (admin eKey required) |
get_auto_lock_limits() |
Min/max auto-lock delay this lock accepts (AutoLockLimits, admin eKey required) |
add_passcode(...) / delete_passcode(...) / clear_passcodes() |
Manage keypad passcodes (admin eKey required) |
get_operation_log() |
Paginated on-device operation log (list[LogEntry]) |
set_lock_sound(enabled) |
Turn the keypad/lock beep on or off (admin eKey required) |
get_lock_time() / calibrate_time(local_time) / sync_time(local_time=…) |
Read / align the lock's clock — the reference is the lock's local time, and writing it requires an admin eKey |
get_device_info() |
Standard BLE Device Information Service fields (DeviceInfo) — no TTLock handshake needed |
add_event_listener(cb) / remove_event_listener(cb) |
Subscribe to LockEvent pushes |
is_connected |
Property — True while a connection is open |
TTLockCloud (HTTP, bootstrap only)
| Method | Purpose |
|---|---|
login(email, password) |
Authenticate; caches the access token |
request_login_verification_code(email) |
Email/SMS a new-device login code |
validate_new_device(email, code) |
Register this machine with the code |
discover_site() |
Resolve the regional API base URL / site |
list_keys() |
Fetch the account's eKeys as list[VirtualKey] |
aclose() |
Release the HTTP connection pool |
Models & enums
- Models:
VirtualKey,LockVersion,SiteInfo,LockAdvertisement,LockEvent,LogEntry,DeviceInfo,AutoLockLimits - Enums:
LockState,AutoLockOperate,KeyboardPwdType,LogOperate,PwdOperateType - Exceptions:
TTLockError(BLE / protocol),CloudError(cloud HTTP)
Home Assistant
This SDK is the transport layer for the ha-ttlock-ble
custom integration. The integration owns BLE discovery and feeds a BLEDevice into
TTLockClient.from_ble_device(...), so it never scans on its own.
Development
See CODE_STYLE.md for project conventions.
uv sync
uv run pytest # tests
uv run ruff check . # lint
uv run mypy src # types
License
MIT — see LICENSE.
Metadata
Release files for ttlock-ble 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ttlock_ble-0.3.0.tar.gz | 55.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ttlock_ble-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 125.6 kB
Release files / ttlock_ble-0.3.0.tar.gz
| Download URL | ttlock_ble-0.3.0.tar.gz |
|---|---|
| Size | 55.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ccc86e683c3feea30fa05fe2980800731eca4ed233c7c4e6434af629c913e88a
|
|
BLAKE2b-256 checksum How to use checksums |
46c3dc8175a9cf6161a9096f64d610abb5a7ed48927456f407555c6a7479d6c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ttlock_ble-0.3.0-py3-none-any.whl
| Download URL | ttlock_ble-0.3.0-py3-none-any.whl |
|---|---|
| Size | 70.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
09f547eea90a96c1b20a8ee662126678884a633b240ef62ce4658f3e85702d89
|
|
BLAKE2b-256 checksum How to use checksums |
448fabb2a6ec2d45e03fc1007c14d195acda5cfe908c38444312153b42324c98
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|