Skip to content

fix(thermo): follow OWNd's dimension 22 crash-form detector instead of blocking every split-control frame - #849

Merged
GreenGrassBlueOcean merged 2 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/dim22-guard-crash-form
Nov 3, 2026
Merged

GreenGrassBlueOcean merged 2 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/dim22-guard-crash-form

Conversation

@gdluck

@gdluck gdluck commented Nov 3, 2026

Copy link
Copy Markdown

Requested by A

What is the problem

Before: services.py, websocket.py and where_grammar.py refuse every WHO 4 dimension 22 frame with one regex (^\*#4\*[^*]*\*#?22(\*[^#]*)?##$), split form included, and the trigger validation calls dimension 22 "a status dimension, not an actuator write command". That was the right guard when the only knowledge was the two F461 crash frames of OWNd#136. Since OWNd#151 (2.0.0b12) the firmware oracle has replayed every dimension 22 spelling on the twelve emulated gateways: every WHERE without an actuator token (zone *#4*Z*22##, general *#4*0*22##, central unit *#4*#0*22##, read or write) kills bt_termo, while the published split forms *#4*3#Z#N*22## and *#4*3#Z#N*#22*MOD*SP*VEL*SWING## crashed none and are forwarded by ten of twelve. OWNd exports that split as is_who4_dimension_22_crash_form and gates its own send paths with it; MyHOME still blocks the forwarded form, and the "status dimension" label contradicts the specification (dimension 22 is read/write, WHO 4 pp. 66-68). The #805 review asked for exactly this follow-up ("the MyHOME-side guard must switch to OWNd.message.is_who4_dimension_22_crash_form consistently").

After: services.is_refused_dimension_22(frame) is the one guard. With an OWNd that exports the detector (2.0.0b12 or newer) it refuses what OWNd refuses and lets the published split form through; with the pinned 2.0.0b11, which has no such detector, the blanket regex stays and every dimension 22 frame is still refused, so nothing changes for a current install until the OWNd pin moves (#848). The WebSocket error, the service log line, the trigger-validation error and the bus-monitor doc say what is blocked and why; gateway.request_zone_dimension keeps refusing dimension 22 for a zone WHERE (that is the crash form by construction).

What is the solution

  • services.py: _is_dimension_22_crash_form imported from OWNd.message when available, is_refused_dimension_22() wraps it with the regex fallback; handle_send_message uses it.
  • websocket.py: ws_bus_monitor_send uses it; error text names the crash form.
  • where_grammar.py: validate_thermo_trigger_command accepts the split-unit write and refuses the crash forms with a message that no longer calls dimension 22 a status dimension (lazy import of services inside the function, so no import cycle at module load).
  • gateway.py: comment and message of the zone-request refusal corrected.
  • docs/configuration/bus_monitor.md: the safety note describes the two classes of frames.
  • tests/test_dimension22_guard.py: crash forms refused and split forms passed with the detector; every dimension 22 frame refused without it (monkeypatched); WebSocket and trigger validation both ways; non-dimension-22 frames untouched. tests/test_websocket.py expects the new error text.

State Diagram

stateDiagram-v2
    [*] --> Frame: dimension 22 frame
    Frame --> Detector: OWNd exports is_who4_dimension_22_crash_form
    Frame --> Blanket: OWNd 2.0.0b11
    Detector --> Refused: WHERE without actuator token (Z, 0, #0, #Z, Z#N)
    Detector --> Forwarded: 3#Z#N request or 4-field write
    Blanket --> Refused: every spelling
Loading

Hardware Verification

None sent to hardware. The split-form verdicts are the oracle's (thermo-dim22 suite, harness=full, OWNd#151 / own-firmware-oracle#68); the crash on the zone form is the live F461 2.0.11 of OWNd#136 (tests/fixtures/traces/f461_live_2026-10-15). No dimension 22 frame was sent to any gateway for this change.

How is it tested

  • New tests/test_dimension22_guard.py: 30 tests with the detector present (crash forms refused, split forms passed, WebSocket forwards the split form and blocks the zone form, trigger validation accepts the split write), 17 of them also run without the detector (monkeypatched) and 13 skip when the installed OWNd has none.
  • Full suite under the pinned OWNd 2.0.0b11: 5263 passed, 13 skipped (the detector-only tests).
  • Full suite with OWNd master (2.0.0b12 + Credentials - Credenziali #153) on PYTHONPATH, the way the myhome-conformance job runs it: 5275 passed, 1 deselected (test_check_all_documentation_real_tree, the known pin-bump signal).
  • ruff check clean.
  • Second-opinion review: three wording findings (contradictory trigger error on b11, refusal log naming the wrong reason, gateway comment), all applied; no functional finding.

Where are the traces

No new trace: the change is in the guard, not in bus traffic. The existing f461_live_2026-10-15 capture stays the evidence for the crash form.

What does the Firmware Oracle / Encyclopedia say

Oracle thermo-dim22 (11 steps, 12 gateways, two byte-identical runs): the four WHERE forms without an actuator token crash bt_termo on nine gateways; *#4*3#Z#N*22## goes out on ten of twelve as a D1 telegram to zone Z, actuator N; the prefix-less Z#N is nack, silent on eleven. Encyclopedia functional/who-4-temperature-control/dimensions.md: dimension 22 is the split-control read/write dimension with the MOD*SP*VEL*SWING payload; the WHERE for it is 3#Z#N.

Checklist

🤖 Generated with Claude Code

gdluck and others added 2 commits November 3, 2026 15:56
…f blocking every split-control frame

Every WHO 4 dimension 22 spelling whose WHERE carries no actuator token
(zone, general 0, central unit #0) kills bt_termo on the Linux IP
gateways; the published split forms *michnovka#4*3#Z#N*22## and
*michnovka#4*3#Z#N*OpenWebNet-HA#22*MOD*SP*VEL*SWING## crashed none of the twelve emulated
gateways (own-firmware-oracle thermo-dim22, OWNd#151). OWNd >= 2.0.0b12
exports that split as is_who4_dimension_22_crash_form and gates its own
send paths with it; MyHOME still refused every dimension 22 frame with
one regex and called the dimension "a status dimension", which the
specification contradicts (read/write, WHO 4 pp. 66-68). The OpenWebNet-HA#805
review asked for the switch to OWNd's detector.

services.is_refused_dimension_22() is now the one guard: it follows the
OWNd detector when the installed OWNd has it and keeps the blanket regex
on 2.0.0b11 (the pinned version), so nothing changes for a current
install until the pin moves. send_message, the bus-monitor WebSocket and
the thermo switch trigger validation use it; the refusal texts say which
forms are blocked and why, and the zone-request refusal in gateway.py
keeps refusing dimension 22 for a zone WHERE. Tests cover both OWNd
generations (the detector path skips on 2.0.0b11).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-strict passes on OWNd 2.0.0b11

The try/except import of is_who4_dimension_22_crash_form fails the
strict-typing ratchet on the pinned OWNd, which has no such attribute
(attr-defined). A typed getattr on OWNd.message keeps the same
behaviour on both OWNd generations.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as ready for review November 3, 2026 20:39
@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit b5ef513 into OpenWebNet-HA:v2-phase1-architecture Nov 3, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants