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.
- localtrv-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +66 -0
- localtrv-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- localtrv-0.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +21 -0
- localtrv-0.2.0/.github/workflows/ci.yml +44 -0
- localtrv-0.2.0/.github/workflows/release.yml +84 -0
- localtrv-0.2.0/.gitignore +42 -0
- localtrv-0.2.0/.pre-commit-config.yaml +20 -0
- localtrv-0.2.0/.python-version +1 -0
- localtrv-0.2.0/CONTRIBUTING.md +124 -0
- localtrv-0.2.0/LICENSE +21 -0
- localtrv-0.2.0/PKG-INFO +316 -0
- localtrv-0.2.0/README.md +294 -0
- localtrv-0.2.0/SECURITY.md +35 -0
- localtrv-0.2.0/custom_components/__init__.py +1 -0
- localtrv-0.2.0/custom_components/localtrv/__init__.py +252 -0
- localtrv-0.2.0/custom_components/localtrv/binary_sensor.py +83 -0
- localtrv-0.2.0/custom_components/localtrv/brand/dark_logo.png +0 -0
- localtrv-0.2.0/custom_components/localtrv/brand/dark_logo.svg +22 -0
- localtrv-0.2.0/custom_components/localtrv/brand/dark_logo@2x.png +0 -0
- localtrv-0.2.0/custom_components/localtrv/brand/icon.png +0 -0
- localtrv-0.2.0/custom_components/localtrv/brand/icon.svg +18 -0
- localtrv-0.2.0/custom_components/localtrv/brand/icon@2x.png +0 -0
- localtrv-0.2.0/custom_components/localtrv/brand/logo.png +0 -0
- localtrv-0.2.0/custom_components/localtrv/brand/logo.svg +22 -0
- localtrv-0.2.0/custom_components/localtrv/brand/logo@2x.png +0 -0
- localtrv-0.2.0/custom_components/localtrv/climate.py +198 -0
- localtrv-0.2.0/custom_components/localtrv/config_flow.py +363 -0
- localtrv-0.2.0/custom_components/localtrv/const.py +66 -0
- localtrv-0.2.0/custom_components/localtrv/coordinator.py +180 -0
- localtrv-0.2.0/custom_components/localtrv/diagnostics.py +251 -0
- localtrv-0.2.0/custom_components/localtrv/entity.py +91 -0
- localtrv-0.2.0/custom_components/localtrv/icons.json +23 -0
- localtrv-0.2.0/custom_components/localtrv/manifest.json +21 -0
- localtrv-0.2.0/custom_components/localtrv/sensor.py +135 -0
- localtrv-0.2.0/custom_components/localtrv/strings.json +152 -0
- localtrv-0.2.0/custom_components/localtrv/translations/en.json +152 -0
- localtrv-0.2.0/docs/protocol.md +561 -0
- localtrv-0.2.0/hacs.json +4 -0
- localtrv-0.2.0/pyproject.toml +83 -0
- localtrv-0.2.0/scripts/check_secrets.py +66 -0
- localtrv-0.2.0/scripts/extract_npdu_fixtures.py +272 -0
- localtrv-0.2.0/src/localtrv/__init__.py +10 -0
- localtrv-0.2.0/src/localtrv/__main__.py +7 -0
- localtrv-0.2.0/src/localtrv/bridge.py +292 -0
- localtrv-0.2.0/src/localtrv/bridge_cache.py +244 -0
- localtrv-0.2.0/src/localtrv/bridge_discovery.py +435 -0
- localtrv-0.2.0/src/localtrv/bridge_owner.py +154 -0
- localtrv-0.2.0/src/localtrv/bridge_poll.py +268 -0
- localtrv-0.2.0/src/localtrv/bridge_setpoint.py +179 -0
- localtrv-0.2.0/src/localtrv/bridge_state.py +414 -0
- localtrv-0.2.0/src/localtrv/bridge_types.py +152 -0
- localtrv-0.2.0/src/localtrv/bridge_verify.py +100 -0
- localtrv-0.2.0/src/localtrv/cli.py +201 -0
- localtrv-0.2.0/src/localtrv/py.typed +0 -0
- localtrv-0.2.0/src/localtrv/testing/__init__.py +41 -0
- localtrv-0.2.0/src/localtrv/testing/errors.py +28 -0
- localtrv-0.2.0/src/localtrv/testing/gatt.py +534 -0
- localtrv-0.2.0/src/localtrv/testing/gatt_client.py +192 -0
- localtrv-0.2.0/src/localtrv/testing/gatt_faults.py +93 -0
- localtrv-0.2.0/src/localtrv/testing/gatt_services.py +218 -0
- localtrv-0.2.0/src/localtrv/testing/network.py +477 -0
- localtrv-0.2.0/src/localtrv/testing/network_aps.py +131 -0
- localtrv-0.2.0/src/localtrv/testing/network_frames.py +319 -0
- localtrv-0.2.0/src/localtrv/testing/network_link.py +203 -0
- localtrv-0.2.0/src/localtrv/testing/network_recorder.py +70 -0
- localtrv-0.2.0/src/localtrv/testing/network_topology.py +98 -0
- localtrv-0.2.0/src/localtrv/testing/scenario.py +128 -0
- localtrv-0.2.0/src/localtrv/testing/valve.py +464 -0
- localtrv-0.2.0/src/localtrv/testing/valve_table.py +124 -0
- localtrv-0.2.0/src/localtrv/testing/zdd.py +241 -0
- localtrv-0.2.0/src/localtrv/valves/__init__.py +210 -0
- localtrv-0.2.0/src/localtrv/valves/constants.py +230 -0
- localtrv-0.2.0/src/localtrv/valves/decode.py +226 -0
- localtrv-0.2.0/src/localtrv/valves/models.py +207 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/__init__.py +167 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/clock.py +349 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/__init__.py +256 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/_cursor.py +72 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/aps.py +238 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/commissioning.py +332 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/fallback.py +315 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/nwk.py +231 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/nwk_commands.py +452 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/nwk_mesh.py +159 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/tlv.py +132 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zcl.py +433 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zcl_types.py +218 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zdo.py +460 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/codec/zdo_mgmt.py +166 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/connection.py +324 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/connection_backoff.py +114 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/connection_failure.py +63 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/connection_session.py +189 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/connection_supervisor.py +473 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/connection_types.py +137 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/counters.py +100 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/crypto.py +320 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/debug.py +360 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/discovery.py +136 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/dispatch.py +454 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/errors.py +211 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/frames.py +213 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/mmo.py +81 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/node.py +278 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/node_acks.py +111 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/node_core.py +294 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/node_join.py +184 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/node_match.py +173 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/node_requests.py +354 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/node_types.py +250 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/redact.py +363 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/requests.py +513 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/routing.py +457 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/session.py +442 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/transport.py +543 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/transport_handshake.py +191 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/transport_writer.py +203 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/types.py +113 -0
- localtrv-0.2.0/src/localtrv/zigbee_direct/uuids.py +67 -0
- localtrv-0.2.0/tests/components/localtrv/bluetooth_helpers.py +80 -0
- localtrv-0.2.0/tests/components/localtrv/conftest.py +239 -0
- localtrv-0.2.0/tests/components/localtrv/test_binary_sensor.py +117 -0
- localtrv-0.2.0/tests/components/localtrv/test_climate.py +368 -0
- localtrv-0.2.0/tests/components/localtrv/test_config_flow.py +488 -0
- localtrv-0.2.0/tests/components/localtrv/test_coordinator.py +223 -0
- localtrv-0.2.0/tests/components/localtrv/test_diagnostics.py +163 -0
- localtrv-0.2.0/tests/components/localtrv/test_e2e_ha.py +223 -0
- localtrv-0.2.0/tests/components/localtrv/test_init.py +503 -0
- localtrv-0.2.0/tests/components/localtrv/test_options_flow.py +127 -0
- localtrv-0.2.0/tests/components/localtrv/test_scaffold.py +130 -0
- localtrv-0.2.0/tests/components/localtrv/test_sensor.py +159 -0
- localtrv-0.2.0/tests/fixtures/__init__.py +1 -0
- localtrv-0.2.0/tests/fixtures/golden.py +386 -0
- localtrv-0.2.0/tests/test_aps.py +138 -0
- localtrv-0.2.0/tests/test_aps_ack.py +140 -0
- localtrv-0.2.0/tests/test_bridge.py +469 -0
- localtrv-0.2.0/tests/test_bridge_cache.py +279 -0
- localtrv-0.2.0/tests/test_bridge_identity.py +104 -0
- localtrv-0.2.0/tests/test_bridge_lease.py +234 -0
- localtrv-0.2.0/tests/test_bridge_owner.py +100 -0
- localtrv-0.2.0/tests/test_bridge_ownership.py +184 -0
- localtrv-0.2.0/tests/test_bridge_ownership_annce.py +155 -0
- localtrv-0.2.0/tests/test_bridge_poll.py +316 -0
- localtrv-0.2.0/tests/test_bridge_review.py +363 -0
- localtrv-0.2.0/tests/test_bridge_setpoint.py +415 -0
- localtrv-0.2.0/tests/test_bridge_verify.py +177 -0
- localtrv-0.2.0/tests/test_bridge_verify_race.py +101 -0
- localtrv-0.2.0/tests/test_check_secrets.py +91 -0
- localtrv-0.2.0/tests/test_cli.py +276 -0
- localtrv-0.2.0/tests/test_clock.py +380 -0
- localtrv-0.2.0/tests/test_commissioning.py +124 -0
- localtrv-0.2.0/tests/test_commissioning_live.py +263 -0
- localtrv-0.2.0/tests/test_connection.py +433 -0
- localtrv-0.2.0/tests/test_connection_failure.py +199 -0
- localtrv-0.2.0/tests/test_connection_faults.py +338 -0
- localtrv-0.2.0/tests/test_connection_review.py +266 -0
- localtrv-0.2.0/tests/test_connection_teardown.py +319 -0
- localtrv-0.2.0/tests/test_connection_validate.py +112 -0
- localtrv-0.2.0/tests/test_counter_model.py +147 -0
- localtrv-0.2.0/tests/test_crypto.py +305 -0
- localtrv-0.2.0/tests/test_debug.py +407 -0
- localtrv-0.2.0/tests/test_debug_redaction.py +342 -0
- localtrv-0.2.0/tests/test_discovery.py +309 -0
- localtrv-0.2.0/tests/test_dispatch.py +387 -0
- localtrv-0.2.0/tests/test_dispatch_acks.py +200 -0
- localtrv-0.2.0/tests/test_dispatch_sender.py +51 -0
- localtrv-0.2.0/tests/test_e2e_aps_ack.py +65 -0
- localtrv-0.2.0/tests/test_e2e_sim.py +469 -0
- localtrv-0.2.0/tests/test_e2e_sim_network.py +387 -0
- localtrv-0.2.0/tests/test_errors.py +48 -0
- localtrv-0.2.0/tests/test_extract_npdu_fixtures.py +202 -0
- localtrv-0.2.0/tests/test_fallback.py +500 -0
- localtrv-0.2.0/tests/test_frames.py +83 -0
- localtrv-0.2.0/tests/test_golden.py +434 -0
- localtrv-0.2.0/tests/test_indirect_retry.py +437 -0
- localtrv-0.2.0/tests/test_mmo.py +133 -0
- localtrv-0.2.0/tests/test_node.py +365 -0
- localtrv-0.2.0/tests/test_node_acks.py +176 -0
- localtrv-0.2.0/tests/test_node_close.py +169 -0
- localtrv-0.2.0/tests/test_node_guard.py +73 -0
- localtrv-0.2.0/tests/test_node_hardening.py +344 -0
- localtrv-0.2.0/tests/test_node_requests.py +514 -0
- localtrv-0.2.0/tests/test_node_routing.py +286 -0
- localtrv-0.2.0/tests/test_node_types.py +46 -0
- localtrv-0.2.0/tests/test_nwk.py +173 -0
- localtrv-0.2.0/tests/test_nwk_commands.py +300 -0
- localtrv-0.2.0/tests/test_nwk_mesh.py +140 -0
- localtrv-0.2.0/tests/test_public_api.py +251 -0
- localtrv-0.2.0/tests/test_redact_mesh.py +55 -0
- localtrv-0.2.0/tests/test_requests.py +350 -0
- localtrv-0.2.0/tests/test_routing_rreq.py +386 -0
- localtrv-0.2.0/tests/test_safe_details.py +304 -0
- localtrv-0.2.0/tests/test_session.py +402 -0
- localtrv-0.2.0/tests/test_sim_gatt.py +429 -0
- localtrv-0.2.0/tests/test_sim_gatt_faults.py +323 -0
- localtrv-0.2.0/tests/test_sim_gatt_tunnel.py +200 -0
- localtrv-0.2.0/tests/test_sim_network.py +532 -0
- localtrv-0.2.0/tests/test_sim_network_aps.py +185 -0
- localtrv-0.2.0/tests/test_sim_network_scenario.py +74 -0
- localtrv-0.2.0/tests/test_sim_valve.py +379 -0
- localtrv-0.2.0/tests/test_smoke.py +6 -0
- localtrv-0.2.0/tests/test_testing.py +105 -0
- localtrv-0.2.0/tests/test_tlv.py +114 -0
- localtrv-0.2.0/tests/test_transport.py +411 -0
- localtrv-0.2.0/tests/test_transport_check.py +54 -0
- localtrv-0.2.0/tests/test_transport_ingress.py +283 -0
- localtrv-0.2.0/tests/test_transport_writer.py +261 -0
- localtrv-0.2.0/tests/test_types.py +190 -0
- localtrv-0.2.0/tests/test_uuids.py +53 -0
- localtrv-0.2.0/tests/test_valves.py +532 -0
- localtrv-0.2.0/tests/test_zcl.py +397 -0
- localtrv-0.2.0/tests/test_zdo.py +342 -0
- 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,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.
|