Skip to main content

tuya-ble-sdk

Python SDK for Tuya Bluetooth Low Energy devices. It speaks the Tuya BLE GATT protocol directly — handshake, session key, encrypted frames and datapoints — and knows nothing about Home Assistant. It also reads the device credentials off the Tuya account, since the handshake needs values the device never broadcasts.

Consumed by the ha-tuya-ble integration, which pins it from manifest.json.

What it does

One read is one whole session: the client connects, performs the handshake, collects the datapoint report and disconnects. Tuya BLE sensors are battery powered and only listen for a moment after they advertise, so holding a connection open would drain them and occupy a proxy slot for nothing.

from tuya_ble_sdk import TuyaBleClient, TuyaBleCredentials, parse_advertisement

info = parse_advertisement(service_data, manufacturer_data)
client = TuyaBleClient(
    ble_device,
    TuyaBleCredentials(uuid=info.uuid, device_id=device_id, local_key=local_key),
)
data_points = await client.async_read_data_points()

Discovery belongs to the caller: the client takes an already-resolved BLEDevice, which is what lets Home Assistant hand over a device seen through a Bluetooth proxy.

parse_advertisement reads what the advertisement discloses — every field of the result is optional. The uuid is encrypted with the product-id record broadcast beside it, so no cloud call is needed to learn it; the readable product id, however, is only there on an unbound device. One bound to a Tuya account broadcasts an obfuscated value in its place: those bytes still decrypt the uuid, but they name no product, and the caller has to learn what the device is some other way.

Credentials from the account

A session needs a device id and a local key, and no device discloses either: only the Tuya account that owns it does. TuyaBleCloudClient logs into the mobile app gateway with the account's e-mail and password and returns what it knows about every device on it.

from tuya_ble_sdk import TuyaBleCloudClient

async with TuyaBleCloudClient(email, password, country_code, region="us") as cloud:
    devices = await cloud.async_list_devices()

paired = next(device for device in devices if device.uuid == info.uuid)
credentials = TuyaBleCredentials(
    uuid=paired.uuid, device_id=paired.device_id, local_key=paired.local_key
)

The account describes a Bluetooth device with the same uuid its advertisement carries and with its mac, so either one ties an account record to a device seen over the air. The record also names the product_id a bound device stops broadcasting.

Region is the account's data centre — one of us, eu, cn, in, we — and country_code is the calling code the account was registered with (55 for Brazil). Nothing is cached: one client is one login.

Command line

The optional cli extra installs a tuya-ble command:

uv run --extra cli tuya-ble scan
uv run --extra cli tuya-ble read \
    --address AA:BB:CC:DD:EE:FF --device-id <id> --local-key <key>

scan lists every nearby Tuya BLE device with its product id and uuid; read runs one session and prints the datapoints it reported. credentials logs into a Tuya account and prints the device id and local key it holds for each device:

uv run --extra cli tuya-ble credentials --email you@example.com --country-code 55

Not implemented

The device may report datapoints in a signed form (0x8004 / 0x8005) instead of the plain one this SDK reads. Those two commands are recognised and logged, not parsed: the reference implementation disagrees with itself about where the records start inside them, and no device was available to settle it. A device that uses them shows up as a read that reports no datapoint, with the command name in the debug log.

Development

uv sync                     # create .venv and install dependencies
uv run ruff format --check .
uv run ruff check .
uv run mypy src
uv run pytest               # 90 % coverage gate

Runtime dependencies carry a >= floor and nothing else: Home Assistant pins its own transitive dependencies exactly, so an == pin here eventually contradicts HA's pin and the integration stops installing.

Credits

The protocol implementation is derived from PlusPlus-ua/ha_tuya_ble (MIT), itself based on redphx/poc-tuya-ble-fingerbot.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tuya_ble_sdk-0.1.2.tar.gz (141.4 kB view details)

Uploaded Source

Built Distribution

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

tuya_ble_sdk-0.1.2-py3-none-any.whl (41.8 kB view details)

Uploaded Python 3

File details

Details for the file tuya_ble_sdk-0.1.2.tar.gz.

File metadata

  • Download URL: tuya_ble_sdk-0.1.2.tar.gz
  • Upload date:
  • Size: 141.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tuya_ble_sdk-0.1.2.tar.gz
Algorithm Hash digest
SHA256 71c36e53c8203cf608d7c49c28039dd8f1927a9e25eb181e769783900388ab03
MD5 023f5615a2cb874d543c942ed5c83cc8
BLAKE2b-256 b08d9f8880c3fdb116f4f0f725a627fba9f8e72440e4e5efa4007e8b4f34ea9d

See more details on using hashes here.

File details

Details for the file tuya_ble_sdk-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: tuya_ble_sdk-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 41.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tuya_ble_sdk-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 855f3084cb2bf3c6bee22722631dca4c3165868e7afd587d21fb902174a59c56
MD5 cf4e3f9e110493b176a1177d0e1abc0c
BLAKE2b-256 93bf5877aad06cdb04602286f31fba7087d8fbfd0f4d18b4561f6b7e41ae225d

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.2 This release

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