localtrv 0.2.0__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 (213) hide show
  1. localtrv-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +66 -0
  2. localtrv-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  3. localtrv-0.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +21 -0
  4. localtrv-0.2.0/.github/workflows/ci.yml +44 -0
  5. localtrv-0.2.0/.github/workflows/release.yml +84 -0
  6. localtrv-0.2.0/.gitignore +42 -0
  7. localtrv-0.2.0/.pre-commit-config.yaml +20 -0
  8. localtrv-0.2.0/.python-version +1 -0
  9. localtrv-0.2.0/CONTRIBUTING.md +124 -0
  10. localtrv-0.2.0/LICENSE +21 -0
  11. localtrv-0.2.0/PKG-INFO +316 -0
  12. localtrv-0.2.0/README.md +294 -0
  13. localtrv-0.2.0/SECURITY.md +35 -0
  14. localtrv-0.2.0/custom_components/__init__.py +1 -0
  15. localtrv-0.2.0/custom_components/localtrv/__init__.py +252 -0
  16. localtrv-0.2.0/custom_components/localtrv/binary_sensor.py +83 -0
  17. localtrv-0.2.0/custom_components/localtrv/brand/dark_logo.png +0 -0
  18. localtrv-0.2.0/custom_components/localtrv/brand/dark_logo.svg +22 -0
  19. localtrv-0.2.0/custom_components/localtrv/brand/dark_logo@2x.png +0 -0
  20. localtrv-0.2.0/custom_components/localtrv/brand/icon.png +0 -0
  21. localtrv-0.2.0/custom_components/localtrv/brand/icon.svg +18 -0
  22. localtrv-0.2.0/custom_components/localtrv/brand/icon@2x.png +0 -0
  23. localtrv-0.2.0/custom_components/localtrv/brand/logo.png +0 -0
  24. localtrv-0.2.0/custom_components/localtrv/brand/logo.svg +22 -0
  25. localtrv-0.2.0/custom_components/localtrv/brand/logo@2x.png +0 -0
  26. localtrv-0.2.0/custom_components/localtrv/climate.py +198 -0
  27. localtrv-0.2.0/custom_components/localtrv/config_flow.py +363 -0
  28. localtrv-0.2.0/custom_components/localtrv/const.py +66 -0
  29. localtrv-0.2.0/custom_components/localtrv/coordinator.py +180 -0
  30. localtrv-0.2.0/custom_components/localtrv/diagnostics.py +251 -0
  31. localtrv-0.2.0/custom_components/localtrv/entity.py +91 -0
  32. localtrv-0.2.0/custom_components/localtrv/icons.json +23 -0
  33. localtrv-0.2.0/custom_components/localtrv/manifest.json +21 -0
  34. localtrv-0.2.0/custom_components/localtrv/sensor.py +135 -0
  35. localtrv-0.2.0/custom_components/localtrv/strings.json +152 -0
  36. localtrv-0.2.0/custom_components/localtrv/translations/en.json +152 -0
  37. localtrv-0.2.0/docs/protocol.md +561 -0
  38. localtrv-0.2.0/hacs.json +4 -0
  39. localtrv-0.2.0/pyproject.toml +83 -0
  40. localtrv-0.2.0/scripts/check_secrets.py +66 -0
  41. localtrv-0.2.0/scripts/extract_npdu_fixtures.py +272 -0
  42. localtrv-0.2.0/src/localtrv/__init__.py +10 -0
  43. localtrv-0.2.0/src/localtrv/__main__.py +7 -0
  44. localtrv-0.2.0/src/localtrv/bridge.py +292 -0
  45. localtrv-0.2.0/src/localtrv/bridge_cache.py +244 -0
  46. localtrv-0.2.0/src/localtrv/bridge_discovery.py +435 -0
  47. localtrv-0.2.0/src/localtrv/bridge_owner.py +154 -0
  48. localtrv-0.2.0/src/localtrv/bridge_poll.py +268 -0
  49. localtrv-0.2.0/src/localtrv/bridge_setpoint.py +179 -0
  50. localtrv-0.2.0/src/localtrv/bridge_state.py +414 -0
  51. localtrv-0.2.0/src/localtrv/bridge_types.py +152 -0
  52. localtrv-0.2.0/src/localtrv/bridge_verify.py +100 -0
  53. localtrv-0.2.0/src/localtrv/cli.py +201 -0
  54. localtrv-0.2.0/src/localtrv/py.typed +0 -0
  55. localtrv-0.2.0/src/localtrv/testing/__init__.py +41 -0
  56. localtrv-0.2.0/src/localtrv/testing/errors.py +28 -0
  57. localtrv-0.2.0/src/localtrv/testing/gatt.py +534 -0
  58. localtrv-0.2.0/src/localtrv/testing/gatt_client.py +192 -0
  59. localtrv-0.2.0/src/localtrv/testing/gatt_faults.py +93 -0
  60. localtrv-0.2.0/src/localtrv/testing/gatt_services.py +218 -0
  61. localtrv-0.2.0/src/localtrv/testing/network.py +477 -0
  62. localtrv-0.2.0/src/localtrv/testing/network_aps.py +131 -0
  63. localtrv-0.2.0/src/localtrv/testing/network_frames.py +319 -0
  64. localtrv-0.2.0/src/localtrv/testing/network_link.py +203 -0
  65. localtrv-0.2.0/src/localtrv/testing/network_recorder.py +70 -0
  66. localtrv-0.2.0/src/localtrv/testing/network_topology.py +98 -0
  67. localtrv-0.2.0/src/localtrv/testing/scenario.py +128 -0
  68. localtrv-0.2.0/src/localtrv/testing/valve.py +464 -0
  69. localtrv-0.2.0/src/localtrv/testing/valve_table.py +124 -0
  70. localtrv-0.2.0/src/localtrv/testing/zdd.py +241 -0
  71. localtrv-0.2.0/src/localtrv/valves/__init__.py +210 -0
  72. localtrv-0.2.0/src/localtrv/valves/constants.py +230 -0
  73. localtrv-0.2.0/src/localtrv/valves/decode.py +226 -0
  74. localtrv-0.2.0/src/localtrv/valves/models.py +207 -0
  75. localtrv-0.2.0/src/localtrv/zigbee_direct/__init__.py +167 -0
  76. localtrv-0.2.0/src/localtrv/zigbee_direct/clock.py +349 -0
  77. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/__init__.py +256 -0
  78. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/_cursor.py +72 -0
  79. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/aps.py +238 -0
  80. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/commissioning.py +332 -0
  81. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/fallback.py +315 -0
  82. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/nwk.py +231 -0
  83. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/nwk_commands.py +452 -0
  84. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/nwk_mesh.py +159 -0
  85. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/tlv.py +132 -0
  86. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zcl.py +433 -0
  87. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zcl_types.py +218 -0
  88. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zdo.py +460 -0
  89. localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zdo_mgmt.py +166 -0
  90. localtrv-0.2.0/src/localtrv/zigbee_direct/connection.py +324 -0
  91. localtrv-0.2.0/src/localtrv/zigbee_direct/connection_backoff.py +114 -0
  92. localtrv-0.2.0/src/localtrv/zigbee_direct/connection_failure.py +63 -0
  93. localtrv-0.2.0/src/localtrv/zigbee_direct/connection_session.py +189 -0
  94. localtrv-0.2.0/src/localtrv/zigbee_direct/connection_supervisor.py +473 -0
  95. localtrv-0.2.0/src/localtrv/zigbee_direct/connection_types.py +137 -0
  96. localtrv-0.2.0/src/localtrv/zigbee_direct/counters.py +100 -0
  97. localtrv-0.2.0/src/localtrv/zigbee_direct/crypto.py +320 -0
  98. localtrv-0.2.0/src/localtrv/zigbee_direct/debug.py +360 -0
  99. localtrv-0.2.0/src/localtrv/zigbee_direct/discovery.py +136 -0
  100. localtrv-0.2.0/src/localtrv/zigbee_direct/dispatch.py +454 -0
  101. localtrv-0.2.0/src/localtrv/zigbee_direct/errors.py +211 -0
  102. localtrv-0.2.0/src/localtrv/zigbee_direct/frames.py +213 -0
  103. localtrv-0.2.0/src/localtrv/zigbee_direct/mmo.py +81 -0
  104. localtrv-0.2.0/src/localtrv/zigbee_direct/node.py +278 -0
  105. localtrv-0.2.0/src/localtrv/zigbee_direct/node_acks.py +111 -0
  106. localtrv-0.2.0/src/localtrv/zigbee_direct/node_core.py +294 -0
  107. localtrv-0.2.0/src/localtrv/zigbee_direct/node_join.py +184 -0
  108. localtrv-0.2.0/src/localtrv/zigbee_direct/node_match.py +173 -0
  109. localtrv-0.2.0/src/localtrv/zigbee_direct/node_requests.py +354 -0
  110. localtrv-0.2.0/src/localtrv/zigbee_direct/node_types.py +250 -0
  111. localtrv-0.2.0/src/localtrv/zigbee_direct/redact.py +363 -0
  112. localtrv-0.2.0/src/localtrv/zigbee_direct/requests.py +513 -0
  113. localtrv-0.2.0/src/localtrv/zigbee_direct/routing.py +457 -0
  114. localtrv-0.2.0/src/localtrv/zigbee_direct/session.py +442 -0
  115. localtrv-0.2.0/src/localtrv/zigbee_direct/transport.py +543 -0
  116. localtrv-0.2.0/src/localtrv/zigbee_direct/transport_handshake.py +191 -0
  117. localtrv-0.2.0/src/localtrv/zigbee_direct/transport_writer.py +203 -0
  118. localtrv-0.2.0/src/localtrv/zigbee_direct/types.py +113 -0
  119. localtrv-0.2.0/src/localtrv/zigbee_direct/uuids.py +67 -0
  120. localtrv-0.2.0/tests/components/localtrv/bluetooth_helpers.py +80 -0
  121. localtrv-0.2.0/tests/components/localtrv/conftest.py +239 -0
  122. localtrv-0.2.0/tests/components/localtrv/test_binary_sensor.py +117 -0
  123. localtrv-0.2.0/tests/components/localtrv/test_climate.py +368 -0
  124. localtrv-0.2.0/tests/components/localtrv/test_config_flow.py +488 -0
  125. localtrv-0.2.0/tests/components/localtrv/test_coordinator.py +223 -0
  126. localtrv-0.2.0/tests/components/localtrv/test_diagnostics.py +163 -0
  127. localtrv-0.2.0/tests/components/localtrv/test_e2e_ha.py +223 -0
  128. localtrv-0.2.0/tests/components/localtrv/test_init.py +503 -0
  129. localtrv-0.2.0/tests/components/localtrv/test_options_flow.py +127 -0
  130. localtrv-0.2.0/tests/components/localtrv/test_scaffold.py +130 -0
  131. localtrv-0.2.0/tests/components/localtrv/test_sensor.py +159 -0
  132. localtrv-0.2.0/tests/fixtures/__init__.py +1 -0
  133. localtrv-0.2.0/tests/fixtures/golden.py +386 -0
  134. localtrv-0.2.0/tests/test_aps.py +138 -0
  135. localtrv-0.2.0/tests/test_aps_ack.py +140 -0
  136. localtrv-0.2.0/tests/test_bridge.py +469 -0
  137. localtrv-0.2.0/tests/test_bridge_cache.py +279 -0
  138. localtrv-0.2.0/tests/test_bridge_identity.py +104 -0
  139. localtrv-0.2.0/tests/test_bridge_lease.py +234 -0
  140. localtrv-0.2.0/tests/test_bridge_owner.py +100 -0
  141. localtrv-0.2.0/tests/test_bridge_ownership.py +184 -0
  142. localtrv-0.2.0/tests/test_bridge_ownership_annce.py +155 -0
  143. localtrv-0.2.0/tests/test_bridge_poll.py +316 -0
  144. localtrv-0.2.0/tests/test_bridge_review.py +363 -0
  145. localtrv-0.2.0/tests/test_bridge_setpoint.py +415 -0
  146. localtrv-0.2.0/tests/test_bridge_verify.py +177 -0
  147. localtrv-0.2.0/tests/test_bridge_verify_race.py +101 -0
  148. localtrv-0.2.0/tests/test_check_secrets.py +91 -0
  149. localtrv-0.2.0/tests/test_cli.py +276 -0
  150. localtrv-0.2.0/tests/test_clock.py +380 -0
  151. localtrv-0.2.0/tests/test_commissioning.py +124 -0
  152. localtrv-0.2.0/tests/test_commissioning_live.py +263 -0
  153. localtrv-0.2.0/tests/test_connection.py +433 -0
  154. localtrv-0.2.0/tests/test_connection_failure.py +199 -0
  155. localtrv-0.2.0/tests/test_connection_faults.py +338 -0
  156. localtrv-0.2.0/tests/test_connection_review.py +266 -0
  157. localtrv-0.2.0/tests/test_connection_teardown.py +319 -0
  158. localtrv-0.2.0/tests/test_connection_validate.py +112 -0
  159. localtrv-0.2.0/tests/test_counter_model.py +147 -0
  160. localtrv-0.2.0/tests/test_crypto.py +305 -0
  161. localtrv-0.2.0/tests/test_debug.py +407 -0
  162. localtrv-0.2.0/tests/test_debug_redaction.py +342 -0
  163. localtrv-0.2.0/tests/test_discovery.py +309 -0
  164. localtrv-0.2.0/tests/test_dispatch.py +387 -0
  165. localtrv-0.2.0/tests/test_dispatch_acks.py +200 -0
  166. localtrv-0.2.0/tests/test_dispatch_sender.py +51 -0
  167. localtrv-0.2.0/tests/test_e2e_aps_ack.py +65 -0
  168. localtrv-0.2.0/tests/test_e2e_sim.py +469 -0
  169. localtrv-0.2.0/tests/test_e2e_sim_network.py +387 -0
  170. localtrv-0.2.0/tests/test_errors.py +48 -0
  171. localtrv-0.2.0/tests/test_extract_npdu_fixtures.py +202 -0
  172. localtrv-0.2.0/tests/test_fallback.py +500 -0
  173. localtrv-0.2.0/tests/test_frames.py +83 -0
  174. localtrv-0.2.0/tests/test_golden.py +434 -0
  175. localtrv-0.2.0/tests/test_indirect_retry.py +437 -0
  176. localtrv-0.2.0/tests/test_mmo.py +133 -0
  177. localtrv-0.2.0/tests/test_node.py +365 -0
  178. localtrv-0.2.0/tests/test_node_acks.py +176 -0
  179. localtrv-0.2.0/tests/test_node_close.py +169 -0
  180. localtrv-0.2.0/tests/test_node_guard.py +73 -0
  181. localtrv-0.2.0/tests/test_node_hardening.py +344 -0
  182. localtrv-0.2.0/tests/test_node_requests.py +514 -0
  183. localtrv-0.2.0/tests/test_node_routing.py +286 -0
  184. localtrv-0.2.0/tests/test_node_types.py +46 -0
  185. localtrv-0.2.0/tests/test_nwk.py +173 -0
  186. localtrv-0.2.0/tests/test_nwk_commands.py +300 -0
  187. localtrv-0.2.0/tests/test_nwk_mesh.py +140 -0
  188. localtrv-0.2.0/tests/test_public_api.py +251 -0
  189. localtrv-0.2.0/tests/test_redact_mesh.py +55 -0
  190. localtrv-0.2.0/tests/test_requests.py +350 -0
  191. localtrv-0.2.0/tests/test_routing_rreq.py +386 -0
  192. localtrv-0.2.0/tests/test_safe_details.py +304 -0
  193. localtrv-0.2.0/tests/test_session.py +402 -0
  194. localtrv-0.2.0/tests/test_sim_gatt.py +429 -0
  195. localtrv-0.2.0/tests/test_sim_gatt_faults.py +323 -0
  196. localtrv-0.2.0/tests/test_sim_gatt_tunnel.py +200 -0
  197. localtrv-0.2.0/tests/test_sim_network.py +532 -0
  198. localtrv-0.2.0/tests/test_sim_network_aps.py +185 -0
  199. localtrv-0.2.0/tests/test_sim_network_scenario.py +74 -0
  200. localtrv-0.2.0/tests/test_sim_valve.py +379 -0
  201. localtrv-0.2.0/tests/test_smoke.py +6 -0
  202. localtrv-0.2.0/tests/test_testing.py +105 -0
  203. localtrv-0.2.0/tests/test_tlv.py +114 -0
  204. localtrv-0.2.0/tests/test_transport.py +411 -0
  205. localtrv-0.2.0/tests/test_transport_check.py +54 -0
  206. localtrv-0.2.0/tests/test_transport_ingress.py +283 -0
  207. localtrv-0.2.0/tests/test_transport_writer.py +261 -0
  208. localtrv-0.2.0/tests/test_types.py +190 -0
  209. localtrv-0.2.0/tests/test_uuids.py +53 -0
  210. localtrv-0.2.0/tests/test_valves.py +532 -0
  211. localtrv-0.2.0/tests/test_zcl.py +397 -0
  212. localtrv-0.2.0/tests/test_zdo.py +342 -0
  213. localtrv-0.2.0/uv.lock +3721 -0
@@ -0,0 +1,66 @@
1
+ name: Bug report
2
+ description: Something does not work as expected.
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for reporting. Never paste your network key, here or in an attachment.
9
+ Security problems go through private reporting instead (see SECURITY.md).
10
+ - type: input
11
+ id: ha_version
12
+ attributes:
13
+ label: Home Assistant version
14
+ placeholder: "2026.10.1"
15
+ validations:
16
+ required: true
17
+ - type: input
18
+ id: integration_version
19
+ attributes:
20
+ label: Integration version
21
+ placeholder: "0.2.0"
22
+ validations:
23
+ required: true
24
+ - type: input
25
+ id: bluetooth
26
+ attributes:
27
+ label: Bluetooth adapter or proxy
28
+ description: Local adapter model, or ESPHome proxy board and ESPHome version.
29
+ placeholder: "ESP32-C3, ESPHome 2026.9.1"
30
+ validations:
31
+ required: true
32
+ - type: input
33
+ id: mtu
34
+ attributes:
35
+ label: MTU log line
36
+ description: The `connected, mtu=…` line from the debug log.
37
+ placeholder: "connected, mtu=247"
38
+ - type: textarea
39
+ id: what_happened
40
+ attributes:
41
+ label: What happened
42
+ description: What you did, what you expected, and what happened instead.
43
+ validations:
44
+ required: true
45
+ - type: textarea
46
+ id: debug_log
47
+ attributes:
48
+ label: Debug log
49
+ description: |
50
+ Enable debug logging (see the README), reproduce the problem, and paste the relevant part.
51
+ Read it first and remove anything you do not want to share.
52
+ render: text
53
+ - type: textarea
54
+ id: diagnostics
55
+ attributes:
56
+ label: Diagnostics
57
+ description: |
58
+ Settings > Devices & services > LocalTRV, menu, Download diagnostics.
59
+ Drag the file here. It contains no keys, but does contain temperatures and network metadata.
60
+ - type: checkboxes
61
+ id: no_key
62
+ attributes:
63
+ label: Network key
64
+ options:
65
+ - label: I removed my network key and did not paste it anywhere.
66
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Report a security vulnerability
4
+ url: https://github.com/jurgenj136/localtrv/security/advisories/new
5
+ about: Please report security problems privately. See SECURITY.md.
@@ -0,0 +1,21 @@
1
+ name: Feature request
2
+ description: Suggest an improvement or a new feature.
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What do you want to do?
9
+ description: The problem or use case, in your own words.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: solution
14
+ attributes:
15
+ label: Your idea
16
+ description: How you would like it to work.
17
+ - type: textarea
18
+ id: setup
19
+ attributes:
20
+ label: Your setup
21
+ description: Valves, router, gateway, Bluetooth adapter or proxy, if relevant.
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "17 4 * * *"
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v7
18
+ - uses: astral-sh/setup-uv@v10.2.0
19
+ with:
20
+ python-version: "3.14"
21
+ enable-cache: true
22
+ - run: uv sync --locked --all-groups
23
+ - run: uv run ruff check
24
+ - run: uv run ruff format --check
25
+ - run: uv run mypy
26
+ - run: uv run pytest
27
+ - name: Secret check
28
+ run: git ls-files -z | xargs -0 uv run python scripts/check_secrets.py
29
+
30
+ hassfest:
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v7
34
+ - uses: home-assistant/actions/hassfest@master
35
+
36
+ hacs:
37
+ # HACS only serves public repositories and cannot read a private one's files.
38
+ if: ${{ !github.event.repository.private }}
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v7
42
+ - uses: hacs/action@main
43
+ with:
44
+ category: integration
@@ -0,0 +1,84 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v7
15
+ - uses: astral-sh/setup-uv@v10.2.0
16
+ with:
17
+ python-version: "3.14"
18
+ - name: Verify tag, pyproject and manifest versions agree
19
+ env:
20
+ TAG: ${{ github.ref_name }}
21
+ run: |
22
+ python - <<'PY'
23
+ import json, os, sys, tomllib
24
+
25
+ tag = os.environ["TAG"].removeprefix("v")
26
+ py = tomllib.load(open("pyproject.toml", "rb"))["project"]["version"]
27
+ manifest = json.load(open("custom_components/localtrv/manifest.json"))
28
+ problems = []
29
+ if py != tag:
30
+ problems.append(f"pyproject version {py} != tag {tag}")
31
+ if manifest["version"] != tag:
32
+ problems.append(f"manifest version {manifest['version']} != tag {tag}")
33
+ if manifest["requirements"] != [f"localtrv=={tag}"]:
34
+ problems.append(f"manifest requirements {manifest['requirements']} != pin {tag}")
35
+ if problems:
36
+ sys.exit("; ".join(problems))
37
+ PY
38
+ - run: uv sync --locked --all-groups
39
+ - run: uv run ruff check
40
+ - run: uv run ruff format --check
41
+ - run: uv run mypy
42
+ - run: uv run pytest
43
+ - name: Secret check
44
+ run: git ls-files -z | xargs -0 uv run python scripts/check_secrets.py
45
+ - run: uv build
46
+ - uses: actions/upload-artifact@v7
47
+ with:
48
+ name: dist
49
+ path: dist/
50
+ if-no-files-found: error
51
+
52
+ publish-pypi:
53
+ needs: build
54
+ runs-on: ubuntu-latest
55
+ environment: pypi
56
+ permissions:
57
+ id-token: write
58
+ steps:
59
+ - uses: actions/download-artifact@v8
60
+ with:
61
+ name: dist
62
+ path: dist/
63
+ - uses: pypa/gh-action-pypi-publish@release/v1
64
+
65
+ github-release:
66
+ needs: publish-pypi
67
+ runs-on: ubuntu-latest
68
+ permissions:
69
+ contents: write
70
+ steps:
71
+ - uses: actions/download-artifact@v8
72
+ with:
73
+ name: dist
74
+ path: dist/
75
+ - name: Create GitHub release
76
+ env:
77
+ GH_TOKEN: ${{ github.token }}
78
+ TAG: ${{ github.ref_name }}
79
+ run: |
80
+ prerelease=()
81
+ # PEP 440 pre/dev releases (v0.2.0a1, v0.2.0rc1, v0.2.0.dev1) are GitHub prereleases.
82
+ if [[ "$TAG" =~ (a|b|rc|\.dev)[0-9]+$ ]]; then prerelease=(--prerelease); fi
83
+ gh release create "$TAG" dist/* --repo "$GITHUB_REPOSITORY" \
84
+ --title "$TAG" --generate-notes "${prerelease[@]}"
@@ -0,0 +1,42 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ # Tooling caches
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .pytest_cache/
13
+ .uv-cache/
14
+ # Coverage
15
+ .coverage
16
+ .coverage.*
17
+ htmlcov/
18
+ coverage.xml
19
+ # Secrets and captures (never commit)
20
+ .viessmann-local/
21
+ *.key
22
+ *.hex
23
+ *identity*.json
24
+ *.pcap
25
+ *.pcapng
26
+ captures/
27
+ *.capture
28
+ .env
29
+ .env.*
30
+ # OS / editor
31
+ .DS_Store
32
+ .idea/
33
+ .vscode/
34
+ # Home Assistant dev config (spec 2.7)
35
+ .ha-config/
36
+ # Local-only notes and agent instructions (not published)
37
+ AGENTS.md
38
+ CLAUDE.md
39
+ .claude/
40
+ docs/*
41
+ !docs/protocol.md
42
+ !docs/images/
@@ -0,0 +1,20 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.17.0
4
+ hooks:
5
+ - id: ruff-check
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: local
9
+ hooks:
10
+ - id: mypy
11
+ name: mypy
12
+ entry: uv run mypy
13
+ language: system
14
+ types: [python]
15
+ pass_filenames: false
16
+ - id: secret-check
17
+ name: secret check (hex key material)
18
+ entry: uv run python scripts/check_secrets.py
19
+ language: system
20
+ pass_filenames: true
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,124 @@
1
+ # Contributing
2
+
3
+ Thanks for helping. This file covers the development setup, the rules every change follows, and
4
+ the code layout. User documentation lives in the [README](README.md).
5
+
6
+ ## Ground rules
7
+
8
+ - **Never post a network key**, a Commissioning Status dump, or a raw capture in an issue, PR,
9
+ commit or log. If you shared one by accident, tell us at once so we can delete it.
10
+ - **No hardware, BLE or network in tests.** The library ships a simulator
11
+ (`localtrv.testing`: fake R0, fake network and valves) that the tests drive.
12
+ - **Dummy keys and EUIs only.** Public spec test vectors are fine; put `# secret-scan: allow` on
13
+ the same line. `scripts/check_secrets.py` fails on anything else that looks like key material.
14
+ - Key material is wrapped in `SecretBytes` (`repr` and `str` print `<redacted>`). Never log it.
15
+ - [Conventional Commits](https://www.conventionalcommits.org/)
16
+ (`feat|fix|refactor|build|ci|chore|docs|style|perf|test`), small commits.
17
+ - Files under about 500 lines. mypy strict, fully typed, no module-level mutable state.
18
+ - Library and simulator code use the injected `zigbee_direct.clock.Clock`. Never call
19
+ `asyncio.sleep`, `asyncio.timeout` or `loop.call_later` directly. Tests use `VirtualClock`.
20
+ - Add a regression test with every bug fix when it fits.
21
+
22
+ ## Development setup
23
+
24
+ Python 3.14.2+ and [uv](https://docs.astral.sh/uv/). Integration tests use
25
+ [pytest-homeassistant-custom-component](https://pypi.org/project/pytest-homeassistant-custom-component/),
26
+ pinned to Home Assistant 2026.10.x.
27
+
28
+ ```bash
29
+ uv sync
30
+ uv run pre-commit install # git hooks: ruff, mypy, secret check
31
+ ```
32
+
33
+ ## Gate
34
+
35
+ Run before every PR (CI runs the same, plus hassfest and the HACS action):
36
+
37
+ ```bash
38
+ uv run ruff check
39
+ uv run ruff format --check
40
+ uv run mypy
41
+ uv run pytest
42
+ git ls-files -z | xargs -0 uv run python scripts/check_secrets.py
43
+ ```
44
+
45
+ Or the hooks at once: `uv run pre-commit run --all-files` (ruff, mypy, secret check). It does not
46
+ run the tests; run `uv run pytest` as well.
47
+
48
+ ## Run Home Assistant locally
49
+
50
+ Runs HA against the working tree. `.ha-config/` is gitignored.
51
+
52
+ ```bash
53
+ mkdir -p .ha-config/custom_components
54
+ ln -s "$PWD/custom_components/localtrv" .ha-config/custom_components/localtrv
55
+ uv run hass -c .ha-config --skip-pip-packages localtrv
56
+ ```
57
+
58
+ ## Test fixtures from logs
59
+
60
+ `scripts/extract_npdu_fixtures.py` turns logged frames into JSON-lines fixtures. Redaction is
61
+ best-effort: review the output by hand before committing it.
62
+
63
+ From a Home Assistant log with debug logging on:
64
+
65
+ ```bash
66
+ uv run python scripts/extract_npdu_fixtures.py --from-ha-log home-assistant.log > fixtures.jsonl
67
+ ```
68
+
69
+ From other saved terminal output with NPDU lines:
70
+
71
+ ```bash
72
+ uv run python scripts/extract_npdu_fixtures.py capture.txt \
73
+ --redact-eui 00:11:22:33:44:55:66:77 --redact-short 0x1234 > fixtures.jsonl
74
+ ```
75
+
76
+ ## Architecture
77
+
78
+ ```text
79
+ Home Assistant / CLI ubisys R0 (ZDD) Zigbee network
80
+ +--------------------+ BLE GATT +----------------+ NWK +------------------+
81
+ | LocalTRV | ----------> | Zigbee Direct | ------> | coordinator (TC) |
82
+ | (ZVD) | <---------- | router | <------ | ZK03840 valves |
83
+ +--------------------+ AES-CCM +----------------+ +------------------+
84
+ plaintext NPDUs inside the session; the R0 applies NWK security
85
+ ```
86
+
87
+ The network key is used offline to derive the Basic Authorization Key for the BLE session.
88
+ Protocol details: [docs/protocol.md](docs/protocol.md).
89
+
90
+ Layering: `zigbee_direct` holds no Viessmann constants. `bridge.py` orchestrates but takes every
91
+ cluster, attribute, scale, sentinel and model id from the `valves` package. `testing/valve.py` may
92
+ hold simulator fixture data written independently of `valves`. Codec frames are
93
+ `@dataclass(frozen=True, slots=True)` with `encode()` and `decode()`: pure, no I/O.
94
+
95
+ ## Package layout
96
+
97
+ ```text
98
+ src/localtrv/ library (PyPI: localtrv)
99
+ valves/ Viessmann ZK03840 mapping (the only vendor-specific package)
100
+ bridge*.py ViCareBridge: discovery, polling, setpoint transactions, cache
101
+ testing/ simulator: fake R0 (GATT + session crypto), fake network and valves
102
+ cli.py `scan` / `status` CLI (key from env or file only)
103
+ zigbee_direct/ generic Zigbee Direct, no Viessmann knowledge
104
+ crypto.py mmo.py key derivation, SPEKE, AES-MMO, CCM session frames
105
+ session.py sans-IO handshake initiator + SessionCipher
106
+ transport*.py BLE GATT transport (bleak)
107
+ connection*.py supervised connection: backoff, rejoin, repair issues
108
+ node*.py routing.py virtual Zigbee node: join, requests, route replies
109
+ debug.py redact.py NPDU debug logging with redaction
110
+ codec/ tlv, nwk, aps, zdo, zcl, commissioning, fallback
111
+ custom_components/localtrv/ Home Assistant integration (HACS)
112
+ scripts/ check_secrets.py, extract_npdu_fixtures.py
113
+ tests/ unit, simulator and HA tests
114
+ docs/protocol.md protocol notes
115
+ ```
116
+
117
+ ## Docs
118
+
119
+ - [docs/protocol.md](docs/protocol.md): protocol notes
120
+
121
+ ## Releases
122
+
123
+ Maintainer only. `pyproject.toml`, `manifest.json` `version` and the manifest pin
124
+ `localtrv==X.Y.Z` move in lockstep, and a `vX.Y.Z` tag runs `release.yml` (gate, build, PyPI trusted publishing, GitHub release).
localtrv-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jurgenj136
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.