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

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])
set_lock_sound(enabled) Turn the keypad/lock beep on or off (admin eKey required)
get_lock_time() / calibrate_time() / sync_time() Read / align the lock's clock
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
  • 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.2

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.2
File Size Uploaded
ttlock_ble-0.2.2.tar.gz 51.3 kB Details

Built distribution (wheel)

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

Total release size: 117.1 kB

Release files / ttlock_ble-0.2.2.tar.gz

Download URL ttlock_ble-0.2.2.tar.gz
Size 51.3 kB
Tags Source
SHA-256 checksum
How to use checksums
443dc0fb5470a9fcbc8d089d5d85547273b3136f1f760111d3471bc6e0c13ed1
BLAKE2b-256 checksum
How to use checksums
5a1ea26b29e09afa5d8368d56aa99229309ecbeef0449233cbe943b8f31378cc
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.2-py3-none-any.whl

Download URL ttlock_ble-0.2.2-py3-none-any.whl
Size 65.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d37263f50a035c0f0547c9a6cba1de64659388871610b664cd39f6c9130d6553
BLAKE2b-256 checksum
How to use checksums
4373afe017051116013e2f2ba1fdbdb6bee17a1646e44c277252f12c28badab9
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

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

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