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.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

ttlock_ble-0.1.11.tar.gz (44.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ttlock_ble-0.1.11-py3-none-any.whl (56.3 kB view details)

Uploaded Python 3

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

Hashes for ttlock_ble-0.1.11.tar.gz
Algorithm Hash digest
SHA256 21af2da1d62fec8cc6e52c85b9e9ad1d41be5aca544a37713c5105dd5422bc68
MD5 d81e9c7be9db2d832ab131580c39f84d
BLAKE2b-256 27df0fadbaf14c39a799e85a950c7c760e0d6db1754c7984ddf2ed7b3c11c180

See more details on using hashes here.

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

Hashes for ttlock_ble-0.1.11-py3-none-any.whl
Algorithm Hash digest
SHA256 9d25a0a00f6afbcf3693d5cea781a74f75aa251e29349c3da2d7d6bf9e02e83e
MD5 80482e49df7dcff4909753e9b6e7101e
BLAKE2b-256 944c376161dfafbf51200c6659064096654fdc8af8319ce30268e4934ba100a1

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.11 This release

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page