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.12+ 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 |
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 |
add_passcode(...) / delete_passcode(...) / clear_passcodes() |
Manage keypad passcodes |
get_operation_log() |
Paginated on-device operation log (list[LogEntry]) |
get_lock_time() / calibrate_time() / sync_time() |
Read / align the lock's clock |
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 - 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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ttlock_ble-0.1.11.tar.gz.
File metadata
- Download URL: ttlock_ble-0.1.11.tar.gz
- Upload date:
- Size: 44.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21af2da1d62fec8cc6e52c85b9e9ad1d41be5aca544a37713c5105dd5422bc68
|
|
| MD5 |
d81e9c7be9db2d832ab131580c39f84d
|
|
| BLAKE2b-256 |
27df0fadbaf14c39a799e85a950c7c760e0d6db1754c7984ddf2ed7b3c11c180
|
File details
Details for the file ttlock_ble-0.1.11-py3-none-any.whl.
File metadata
- Download URL: ttlock_ble-0.1.11-py3-none-any.whl
- Upload date:
- Size: 56.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d25a0a00f6afbcf3693d5cea781a74f75aa251e29349c3da2d7d6bf9e02e83e
|
|
| MD5 |
80482e49df7dcff4909753e9b6e7101e
|
|
| BLAKE2b-256 |
944c376161dfafbf51200c6659064096654fdc8af8319ce30268e4934ba100a1
|