tuya-ble-sdk 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. tuya_ble_sdk-0.1.1/.github/dependabot.yml +33 -0
  2. tuya_ble_sdk-0.1.1/.github/workflows/ci.yml +29 -0
  3. tuya_ble_sdk-0.1.1/.github/workflows/codeql.yml +18 -0
  4. tuya_ble_sdk-0.1.1/.github/workflows/release.yml +62 -0
  5. tuya_ble_sdk-0.1.1/.gitignore +10 -0
  6. tuya_ble_sdk-0.1.1/.pre-commit-config.yaml +26 -0
  7. tuya_ble_sdk-0.1.1/.release-please-manifest.json +3 -0
  8. tuya_ble_sdk-0.1.1/CHANGELOG.md +33 -0
  9. tuya_ble_sdk-0.1.1/LICENSE +21 -0
  10. tuya_ble_sdk-0.1.1/PKG-INFO +111 -0
  11. tuya_ble_sdk-0.1.1/README.md +84 -0
  12. tuya_ble_sdk-0.1.1/pyproject.toml +140 -0
  13. tuya_ble_sdk-0.1.1/release-please-config.json +67 -0
  14. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/__init__.py +45 -0
  15. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/advertisement.py +75 -0
  16. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/cli.py +160 -0
  17. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/client.py +353 -0
  18. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/commands/__init__.py +18 -0
  19. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/commands/data_points.py +111 -0
  20. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/commands/device_info.py +27 -0
  21. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/commands/pair.py +51 -0
  22. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/commands/time_reply.py +38 -0
  23. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/crypto.py +87 -0
  24. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/exceptions/__init__.py +17 -0
  25. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/exceptions/tuya_ble_authentication_error.py +14 -0
  26. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/exceptions/tuya_ble_connection_error.py +9 -0
  27. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/exceptions/tuya_ble_error.py +12 -0
  28. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/exceptions/tuya_ble_handshake_timeout_error.py +18 -0
  29. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/exceptions/tuya_ble_protocol_error.py +14 -0
  30. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/models/__init__.py +16 -0
  31. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/models/advertisement_info.py +22 -0
  32. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/models/data_point.py +26 -0
  33. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/models/device_info.py +25 -0
  34. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/models/tuya_ble_credentials.py +21 -0
  35. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/protocol/__init__.py +50 -0
  36. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/protocol/command_code.py +36 -0
  37. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/protocol/constants.py +23 -0
  38. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/protocol/data_point_type.py +16 -0
  39. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/protocol/frame.py +154 -0
  40. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/protocol/reassembler.py +83 -0
  41. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/protocol/varint.py +39 -0
  42. tuya_ble_sdk-0.1.1/src/tuya_ble_sdk/py.typed +0 -0
  43. tuya_ble_sdk-0.1.1/uv.lock +1228 -0
@@ -0,0 +1,33 @@
1
+ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
2
+ version: 2
3
+ updates:
4
+ - package-ecosystem: "github-actions"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ day: "monday"
9
+ time: "09:00"
10
+ timezone: "America/Sao_Paulo"
11
+ groups:
12
+ github-actions:
13
+ patterns:
14
+ - "*"
15
+ commit-message:
16
+ prefix: "deps"
17
+ include: "scope"
18
+
19
+ - package-ecosystem: "uv"
20
+ directory: "/"
21
+ schedule:
22
+ interval: "weekly"
23
+ day: "monday"
24
+ time: "09:00"
25
+ timezone: "America/Sao_Paulo"
26
+ groups:
27
+ python-deps:
28
+ patterns:
29
+ - "*"
30
+ commit-message:
31
+ prefix: "deps"
32
+ prefix-development: "deps-dev"
33
+ include: "scope"
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ci-${{ github.ref }}
10
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ lint:
17
+ uses: roquerodrigo/workflows/.github/workflows/python-lint.yml@main
18
+
19
+ tests:
20
+ needs: lint
21
+ uses: roquerodrigo/workflows/.github/workflows/python-test.yml@main
22
+
23
+ update-pr-branch:
24
+ if: github.event_name == 'pull_request'
25
+ needs: [lint, tests]
26
+ permissions:
27
+ contents: write
28
+ pull-requests: write
29
+ uses: roquerodrigo/workflows/.github/workflows/update-pr-branch.yml@main
@@ -0,0 +1,18 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 0 * * 0"
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ codeql:
14
+ permissions:
15
+ actions: read
16
+ contents: read
17
+ security-events: write
18
+ uses: roquerodrigo/workflows/.github/workflows/codeql.yml@main
@@ -0,0 +1,62 @@
1
+ name: Release
2
+
3
+ # Three composable stages: release-please grooms the release pull request and
4
+ # tags it, the lock is refreshed on the release branch so it lands carrying the
5
+ # same version, and the tag is published to PyPI.
6
+ #
7
+ # The `workflow_run.event == 'push'` condition is what stops a pull request with
8
+ # green CI from cutting a release.
9
+
10
+ on:
11
+ workflow_run:
12
+ workflows: [CI]
13
+ types: [completed]
14
+ branches: [main]
15
+ workflow_dispatch:
16
+ inputs:
17
+ tag:
18
+ description: "Tag to republish to PyPI, for example v1.1.0. Skips release-please."
19
+ required: true
20
+ type: string
21
+
22
+ permissions: {}
23
+
24
+ jobs:
25
+ release:
26
+ if: >-
27
+ github.event_name == 'workflow_run' &&
28
+ github.event.workflow_run.event == 'push' &&
29
+ github.event.workflow_run.conclusion == 'success'
30
+ permissions:
31
+ contents: write
32
+ pull-requests: write
33
+ uses: roquerodrigo/workflows/.github/workflows/release-please.yml@main
34
+ secrets:
35
+ release-token: ${{ secrets.RELEASE_PLEASE_PAT }}
36
+
37
+ sync-uv-lock:
38
+ needs: release
39
+ if: needs.release.outputs.release-pr != ''
40
+ permissions:
41
+ contents: read
42
+ uses: roquerodrigo/workflows/.github/workflows/sync-uv-lock.yml@main
43
+ with:
44
+ release-pr: ${{ needs.release.outputs.release-pr }}
45
+ secrets:
46
+ release-token: ${{ secrets.RELEASE_PLEASE_PAT }}
47
+
48
+ publish:
49
+ needs: release
50
+ if: >-
51
+ always() && (
52
+ github.event_name == 'workflow_dispatch' ||
53
+ needs.release.outputs.release-created == 'true'
54
+ )
55
+ permissions:
56
+ contents: read
57
+ uses: roquerodrigo/workflows/.github/workflows/publish-pypi.yml@main
58
+ with:
59
+ package: tuya-ble-sdk
60
+ ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.release.outputs.tag-name }}
61
+ secrets:
62
+ pypi-token: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .env
5
+ dist/
6
+ .coverage
7
+ htmlcov/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .pytest_cache/
@@ -0,0 +1,26 @@
1
+ # Hooks run through `uv run`, so they use the exact ruff and mypy versions
2
+ # pinned in the lint dependency group. A hook that installs its own copy drifts
3
+ # from CI the moment the pin moves.
4
+ repos:
5
+ - repo: local
6
+ hooks:
7
+ - id: ruff-format
8
+ name: ruff format
9
+ entry: uv run ruff format
10
+ language: system
11
+ types_or: [python, pyi]
12
+ require_serial: true
13
+
14
+ - id: ruff-check
15
+ name: ruff check
16
+ entry: uv run ruff check --fix
17
+ language: system
18
+ types_or: [python, pyi]
19
+ require_serial: true
20
+
21
+ - id: mypy
22
+ name: mypy
23
+ entry: uv run mypy
24
+ language: system
25
+ pass_filenames: false
26
+ types: [python]
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.1.1"
3
+ }
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1](https://github.com/roquerodrigo/tuya-ble-sdk/compare/v0.1.0...v0.1.1) (2026-08-19)
4
+
5
+
6
+ ### Features
7
+
8
+ * implement the Tuya BLE protocol ([f309745](https://github.com/roquerodrigo/tuya-ble-sdk/commit/f309745b2a62b67af4cace38ae7ecfca2b8cf7a4))
9
+ * scaffold the Tuya BLE SDK package ([8242a3e](https://github.com/roquerodrigo/tuya-ble-sdk/commit/8242a3e7bcdccdf62f7b4385aee9d6fa2ca7d757))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * connect at the first sighting instead of after the whole scan ([a612982](https://github.com/roquerodrigo/tuya-ble-sdk/commit/a612982a51b0b3ca0fe5c2ac7dbfa345215e3993))
15
+ * connect at the first sighting instead of after the whole scan ([b81dd11](https://github.com/roquerodrigo/tuya-ble-sdk/commit/b81dd1180359b44832d506f8071bd85621221b55))
16
+ * hash the advertised product record as broadcast ([be4c892](https://github.com/roquerodrigo/tuya-ble-sdk/commit/be4c892439a50ec4c163f61044a2d8abd811d085))
17
+ * reset the framing state between two sessions of one client ([57059c2](https://github.com/roquerodrigo/tuya-ble-sdk/commit/57059c2b1a23b0517b31704108efcff9b8ac1fe9))
18
+ * treat a report with no datapoint as an answer ([445ad3a](https://github.com/roquerodrigo/tuya-ble-sdk/commit/445ad3ac31905679a2373c89c0b461aac0883e8e))
19
+
20
+
21
+ ### Documentation
22
+
23
+ * record the unparsed signed datapoint commands ([93db2a9](https://github.com/roquerodrigo/tuya-ble-sdk/commit/93db2a9cfa1530c1ea9e74d6fd6dee1e1df08c94))
24
+
25
+
26
+ ### Tests
27
+
28
+ * freeze the wire bytes of the handshake commands ([18c6e86](https://github.com/roquerodrigo/tuya-ble-sdk/commit/18c6e863cbeef7d372eb4d33ea2aacd15a774484))
29
+
30
+
31
+ ### Miscellaneous Chores
32
+
33
+ * let release-please own the version ([2f0a12a](https://github.com/roquerodrigo/tuya-ble-sdk/commit/2f0a12a8b9de35f55a59fc8fb119aaf5db4958c0))
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rodrigo Roque @roquerodrigo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.5
2
+ Name: tuya-ble-sdk
3
+ Version: 0.1.1
4
+ Summary: Python SDK for Tuya Bluetooth Low Energy devices
5
+ Project-URL: Homepage, https://github.com/roquerodrigo/tuya-ble-sdk
6
+ Project-URL: Repository, https://github.com/roquerodrigo/tuya-ble-sdk
7
+ Project-URL: Issues, https://github.com/roquerodrigo/tuya-ble-sdk/issues
8
+ Author-email: Rodrigo Roque <rodrigogoncalvesroque@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ble,bluetooth,home-automation,sdk,tuya
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Home Automation
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: bleak-retry-connector>=4.4.0
22
+ Requires-Dist: bleak>=3.0.0
23
+ Requires-Dist: cryptography>=44.0.0
24
+ Provides-Extra: cli
25
+ Requires-Dist: typer>=0.12.0; extra == 'cli'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # tuya-ble-sdk
29
+
30
+ Python SDK for **Tuya Bluetooth Low Energy devices**. It speaks the Tuya BLE
31
+ GATT protocol directly — handshake, session key, encrypted frames and
32
+ datapoints — and knows nothing about Home Assistant.
33
+
34
+ Consumed by the [`ha-tuya-ble`](https://github.com/roquerodrigo/ha-tuya-ble)
35
+ integration, which pins it from `manifest.json`.
36
+
37
+ ## What it does
38
+
39
+ One read is one whole session: the client connects, performs the handshake,
40
+ collects the datapoint report and disconnects. Tuya BLE sensors are battery
41
+ powered and only listen for a moment after they advertise, so holding a
42
+ connection open would drain them and occupy a proxy slot for nothing.
43
+
44
+ ```python
45
+ from tuya_ble_sdk import TuyaBleClient, TuyaBleCredentials, parse_advertisement
46
+
47
+ info = parse_advertisement(service_data, manufacturer_data)
48
+ client = TuyaBleClient(
49
+ ble_device,
50
+ TuyaBleCredentials(uuid=info.uuid, device_id=device_id, local_key=local_key),
51
+ )
52
+ data_points = await client.async_read_data_points()
53
+ ```
54
+
55
+ Discovery belongs to the caller: the client takes an already-resolved
56
+ `BLEDevice`, which is what lets Home Assistant hand over a device seen through
57
+ a Bluetooth proxy.
58
+
59
+ `parse_advertisement` reads what the advertisement discloses — every field of
60
+ the result is optional. The uuid is encrypted with the product-id record
61
+ broadcast beside it, so no cloud call is needed to learn it; the readable
62
+ product id, however, is only there on an **unbound** device. One bound to a
63
+ Tuya account broadcasts an obfuscated value in its place: those bytes still
64
+ decrypt the uuid, but they name no product, and the caller has to learn what
65
+ the device is some other way.
66
+
67
+ ## Command line
68
+
69
+ The optional `cli` extra installs a `tuya-ble` command:
70
+
71
+ ```bash
72
+ uv run --extra cli tuya-ble scan
73
+ uv run --extra cli tuya-ble read \
74
+ --address AA:BB:CC:DD:EE:FF --device-id <id> --local-key <key>
75
+ ```
76
+
77
+ `scan` lists every nearby Tuya BLE device with its product id and uuid; `read`
78
+ runs one session and prints the datapoints it reported.
79
+
80
+ ## Not implemented
81
+
82
+ The device may report datapoints in a *signed* form (`0x8004` / `0x8005`)
83
+ instead of the plain one this SDK reads. Those two commands are recognised and
84
+ logged, not parsed: the reference implementation disagrees with itself about
85
+ where the records start inside them, and no device was available to settle it.
86
+ A device that uses them shows up as a read that reports no datapoint, with the
87
+ command name in the debug log.
88
+
89
+ ## Development
90
+
91
+ ```bash
92
+ uv sync # create .venv and install dependencies
93
+ uv run ruff format --check .
94
+ uv run ruff check .
95
+ uv run mypy src
96
+ uv run pytest # 90 % coverage gate
97
+ ```
98
+
99
+ Runtime dependencies carry a `>=` floor and nothing else: Home Assistant pins its
100
+ own transitive dependencies exactly, so an `==` pin here eventually contradicts
101
+ HA's pin and the integration stops installing.
102
+
103
+ ## Credits
104
+
105
+ The protocol implementation is derived from
106
+ [`PlusPlus-ua/ha_tuya_ble`](https://github.com/PlusPlus-ua/ha_tuya_ble) (MIT),
107
+ itself based on [`redphx/poc-tuya-ble-fingerbot`](https://github.com/redphx/poc-tuya-ble-fingerbot).
108
+
109
+ ## License
110
+
111
+ [MIT](LICENSE)
@@ -0,0 +1,84 @@
1
+ # tuya-ble-sdk
2
+
3
+ Python SDK for **Tuya Bluetooth Low Energy devices**. It speaks the Tuya BLE
4
+ GATT protocol directly — handshake, session key, encrypted frames and
5
+ datapoints — and knows nothing about Home Assistant.
6
+
7
+ Consumed by the [`ha-tuya-ble`](https://github.com/roquerodrigo/ha-tuya-ble)
8
+ integration, which pins it from `manifest.json`.
9
+
10
+ ## What it does
11
+
12
+ One read is one whole session: the client connects, performs the handshake,
13
+ collects the datapoint report and disconnects. Tuya BLE sensors are battery
14
+ powered and only listen for a moment after they advertise, so holding a
15
+ connection open would drain them and occupy a proxy slot for nothing.
16
+
17
+ ```python
18
+ from tuya_ble_sdk import TuyaBleClient, TuyaBleCredentials, parse_advertisement
19
+
20
+ info = parse_advertisement(service_data, manufacturer_data)
21
+ client = TuyaBleClient(
22
+ ble_device,
23
+ TuyaBleCredentials(uuid=info.uuid, device_id=device_id, local_key=local_key),
24
+ )
25
+ data_points = await client.async_read_data_points()
26
+ ```
27
+
28
+ Discovery belongs to the caller: the client takes an already-resolved
29
+ `BLEDevice`, which is what lets Home Assistant hand over a device seen through
30
+ a Bluetooth proxy.
31
+
32
+ `parse_advertisement` reads what the advertisement discloses — every field of
33
+ the result is optional. The uuid is encrypted with the product-id record
34
+ broadcast beside it, so no cloud call is needed to learn it; the readable
35
+ product id, however, is only there on an **unbound** device. One bound to a
36
+ Tuya account broadcasts an obfuscated value in its place: those bytes still
37
+ decrypt the uuid, but they name no product, and the caller has to learn what
38
+ the device is some other way.
39
+
40
+ ## Command line
41
+
42
+ The optional `cli` extra installs a `tuya-ble` command:
43
+
44
+ ```bash
45
+ uv run --extra cli tuya-ble scan
46
+ uv run --extra cli tuya-ble read \
47
+ --address AA:BB:CC:DD:EE:FF --device-id <id> --local-key <key>
48
+ ```
49
+
50
+ `scan` lists every nearby Tuya BLE device with its product id and uuid; `read`
51
+ runs one session and prints the datapoints it reported.
52
+
53
+ ## Not implemented
54
+
55
+ The device may report datapoints in a *signed* form (`0x8004` / `0x8005`)
56
+ instead of the plain one this SDK reads. Those two commands are recognised and
57
+ logged, not parsed: the reference implementation disagrees with itself about
58
+ where the records start inside them, and no device was available to settle it.
59
+ A device that uses them shows up as a read that reports no datapoint, with the
60
+ command name in the debug log.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ uv sync # create .venv and install dependencies
66
+ uv run ruff format --check .
67
+ uv run ruff check .
68
+ uv run mypy src
69
+ uv run pytest # 90 % coverage gate
70
+ ```
71
+
72
+ Runtime dependencies carry a `>=` floor and nothing else: Home Assistant pins its
73
+ own transitive dependencies exactly, so an `==` pin here eventually contradicts
74
+ HA's pin and the integration stops installing.
75
+
76
+ ## Credits
77
+
78
+ The protocol implementation is derived from
79
+ [`PlusPlus-ua/ha_tuya_ble`](https://github.com/PlusPlus-ua/ha_tuya_ble) (MIT),
80
+ itself based on [`redphx/poc-tuya-ble-fingerbot`](https://github.com/redphx/poc-tuya-ble-fingerbot).
81
+
82
+ ## License
83
+
84
+ [MIT](LICENSE)
@@ -0,0 +1,140 @@
1
+ [project]
2
+ name = "tuya-ble-sdk"
3
+ version = "0.1.1"
4
+ description = "Python SDK for Tuya Bluetooth Low Energy devices"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Rodrigo Roque", email = "rodrigogoncalvesroque@gmail.com" }]
9
+ keywords = ["tuya", "bluetooth", "ble", "sdk", "home-automation"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Programming Language :: Python :: 3.14",
18
+ "Topic :: Home Automation",
19
+ ]
20
+ # Runtime dependencies carry a floor and nothing else. Home Assistant pins its
21
+ # own transitive dependencies exactly, so an `==` pin — or an upper bound —
22
+ # eventually contradicts HA's pin and the consuming integration stops
23
+ # installing. Exact versions belong in the dependency groups below and in
24
+ # uv.lock, neither of which reaches the consumer.
25
+ dependencies = [
26
+ "bleak>=3.0.0",
27
+ "bleak-retry-connector>=4.4.0",
28
+ "cryptography>=44.0.0",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ cli = [
33
+ "typer>=0.12.0",
34
+ ]
35
+
36
+ [project.scripts]
37
+ tuya-ble = "tuya_ble_sdk.cli:app"
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/roquerodrigo/tuya-ble-sdk"
41
+ Repository = "https://github.com/roquerodrigo/tuya-ble-sdk"
42
+ Issues = "https://github.com/roquerodrigo/tuya-ble-sdk/issues"
43
+
44
+ [build-system]
45
+ requires = ["hatchling"]
46
+ build-backend = "hatchling.build"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/tuya_ble_sdk"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ exclude = ["tests"]
53
+
54
+ [dependency-groups]
55
+ dev = [
56
+ "typer==0.20.0",
57
+ "pytest==9.0.3",
58
+ "pytest-asyncio==1.4.0",
59
+ "pytest-cov==7.1.0",
60
+ "pre-commit==4.6.1",
61
+ ]
62
+ lint = [
63
+ "ruff==0.16.1",
64
+ "mypy==2.3.0",
65
+ ]
66
+
67
+ [tool.uv]
68
+ default-groups = ["dev", "lint"]
69
+
70
+ [tool.pytest.ini_options]
71
+ asyncio_mode = "auto"
72
+ testpaths = ["tests"]
73
+ addopts = [
74
+ "--strict-markers",
75
+ "--cov=src/tuya_ble_sdk",
76
+ "--cov-report=term-missing",
77
+ "--cov-fail-under=90",
78
+ ]
79
+
80
+ [tool.mypy]
81
+ python_version = "3.12"
82
+ files = ["src"]
83
+ strict = true
84
+ warn_unreachable = true
85
+
86
+ [[tool.mypy.overrides]]
87
+ module = "tests.*"
88
+ disallow_untyped_defs = false
89
+ check_untyped_defs = false
90
+
91
+ [tool.ruff]
92
+ target-version = "py312"
93
+ src = ["src", "tests"]
94
+
95
+ [tool.ruff.format]
96
+ exclude = ["*.md"] # ruff 0.16 formats Python blocks in Markdown; docs keep their own layout
97
+
98
+ [tool.ruff.lint]
99
+ select = ["ALL"]
100
+ ignore = [
101
+ "ANN401", # Dynamically typed expressions (typing.Any) are disallowed
102
+ "D203", # no-blank-line-before-class (incompatible with formatter)
103
+ "D212", # multi-line-summary-first-line (incompatible with formatter)
104
+ "COM812", # incompatible with formatter
105
+ "ISC001", # incompatible with formatter
106
+ "CPY001", # copyright headers are not used in this project
107
+ "TID252", # relative imports between same-package siblings are the house style
108
+ ]
109
+
110
+ [tool.ruff.lint.flake8-pytest-style]
111
+ fixture-parentheses = false
112
+
113
+ [tool.ruff.lint.pyupgrade]
114
+ keep-runtime-typing = true
115
+
116
+ [tool.ruff.lint.per-file-ignores]
117
+ "src/tuya_ble_sdk/cli.py" = [
118
+ "FBT001", # a boolean-positional flag is the typer idiom
119
+ "FBT003", # so is the boolean positional passed to typer.Option
120
+ "PLR0913", # a command's options are its signature
121
+ "PLR0917",
122
+ ]
123
+ "tests/**" = [
124
+ "ARG002", # the fake peripheral mirrors bleak's signature, unused arguments included
125
+ "EM101", # a literal message is fine in a throwaway test double
126
+ "FBT001", # so is a boolean flag mirroring bleak's own signature
127
+ "FBT002",
128
+ "PLC0415", # a local import keeps a helper's dependency next to its only use
129
+ "PT018", # a composite assertion reads better than splitting one fact in two
130
+ "S324", # tests recompute the MD5 the protocol specifies
131
+ "TRY003", # a literal message is fine in a throwaway test double
132
+ "S101", # assert is normal in pytest
133
+ "S105", # hardcoded-password-string is fine for fake test credentials
134
+ "S106", # hardcoded-password-func-arg is fine for fake test credentials
135
+ "D", # no docstrings required in tests
136
+ "ANN", # no type annotations required in tests
137
+ "ARG001", # fixtures are used implicitly by pytest
138
+ "SLF001", # testing internal methods is intentional
139
+ "PLR2004", # magic numbers in test assertions are fine
140
+ ]
@@ -0,0 +1,67 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "release-type": "python",
4
+ "include-component-in-tag": false,
5
+ "include-v-in-tag": true,
6
+ "bump-minor-pre-major": true,
7
+ "bump-patch-for-minor-pre-major": true,
8
+ "packages": {
9
+ ".": {
10
+ "package-name": "tuya-ble-sdk",
11
+ "changelog-sections": [
12
+ {
13
+ "type": "feat",
14
+ "section": "Features"
15
+ },
16
+ {
17
+ "type": "fix",
18
+ "section": "Bug Fixes"
19
+ },
20
+ {
21
+ "type": "perf",
22
+ "section": "Performance Improvements"
23
+ },
24
+ {
25
+ "type": "revert",
26
+ "section": "Reverts"
27
+ },
28
+ {
29
+ "type": "refactor",
30
+ "section": "Code Refactoring"
31
+ },
32
+ {
33
+ "type": "deps",
34
+ "section": "Dependencies"
35
+ },
36
+ {
37
+ "type": "deps-dev",
38
+ "section": "Development Dependencies"
39
+ },
40
+ {
41
+ "type": "docs",
42
+ "section": "Documentation"
43
+ },
44
+ {
45
+ "type": "build",
46
+ "section": "Build System"
47
+ },
48
+ {
49
+ "type": "ci",
50
+ "section": "Continuous Integration"
51
+ },
52
+ {
53
+ "type": "test",
54
+ "section": "Tests"
55
+ },
56
+ {
57
+ "type": "style",
58
+ "section": "Styles"
59
+ },
60
+ {
61
+ "type": "chore",
62
+ "section": "Miscellaneous Chores"
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ }