Skip to main content

ttlock-ble

CI PyPI

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

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.

Metadata

Release files for ttlock-ble 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ttlock-ble 0.2.0
File Size Uploaded
ttlock_ble-0.2.0.tar.gz 47.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ttlock-ble 0.2.0
File Interpreter ABI Platform
ttlock_ble-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 109.4 kB

Release files / ttlock_ble-0.2.0.tar.gz

Download URL ttlock_ble-0.2.0.tar.gz
Size 47.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3247f8c9749102e64a511187ff87174e407a1f9c6af6e99e1901af6baa25cc12
BLAKE2b-256 checksum
How to use checksums
9849a686388fe10fce1481f3c59915cd8b2fb96d93347b79ee2a7e2af4e35464
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.2.0-py3-none-any.whl

Download URL ttlock_ble-0.2.0-py3-none-any.whl
Size 61.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
996833038f1604ba811a3cb3d8db8e8b5dad01557794bd1289ab733663e5d511
BLAKE2b-256 checksum
How to use checksums
3ddd75fc1708345e096cd80cdf4890c6e71b406efb84ce8c5538364b01683f9b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page