Repository navigation
fix(heating): WHO 4 decoder no longer raises on empty tags or a wildcard WHERE, reads ASCII digits only; README / conformance-matrix / docstring corrections (Encyclopedia audit, group 1) - #156
GreenGrassBlueOcean merged 14 commits into
Conversation
…imension 19/22 Report 21 H1, H2, H3, H4, H5, H6, H7, H8, H9, H13, H14, H15, H16, H17, H18, H19 (docstring note for H10). OWNHeatingEvent keeps the WHERE in its address class instead of reducing it to one integer zone (Encyclopedia who-4-temperature-control/addressing.md, "Published functional WHERE forms", "MyHOME_Suite address rules", "Actuator addressing", "Historical central-unit variants"): new scope, zone_number, central_unit, probe_number, actuator, interface and address properties; unique_id carries the class (4-001 is not 4-1, 4-0 is not 4-#0, 4-#0#2 is the 4-zone central unit 2, 4-2#1 one actuator). #0#C is the central unit C, not zone C (ENCY_CONFIRM TC-H1: OPEN.db rule 1004 four-zone control unit #0#[ZA][ZB]; ORACLE_CONFIRM H1: *4*303*#0#1## is a central-unit telegram with selector byte 01, distinct from michnovka#1 and #0 on ten of twelve gateways). 3#Z#N under dimension 22 is zone Z, actuator N, and the MOD*SP*VEL*SWING reply is decoded (dimensions.md "DIMENSION 22 - split control"; parse only, the send guard is unchanged). A trailing michnovka#4#NN is a bus interface, not actuator 4. WHAT decoding follows what.md: 102/202/302 are the protection states (mode "protection", operating_mode antifreeze / thermal_protection / protection; ENCY_CONFIRM TC-H5), 103/203/303 off; x10 manual, x11 automatic, x12 timed manual, x15 daily holiday and x3DDD vacation (mode "holiday"), x1PP program, x2SS scenario, 3000 / 3000#P cancel vacation, 3100 / 3200 restore, WHAT 40 release local probe adjustment, each with its own operating_mode value and log line. Dimension 19 values 14/15/16 are fan-coil OFF states (dimensions.md "DIMENSION 19", functional-page-only evidence, ENCY_CONFIRM TC-H8), not fan speeds 9..11; dimension 20 value 9 logs "fan-coil is on"; dimension 15 reads the three-digit value of the documented test report *michnovka#4*11*15*1*123*1111## (unsigned tenths, an inference); empty dimension 11 / 60 values no longer raise out of OWNMessage.parse; dimension writes on the event path (OpenWebNet-HA#14, #0, OpenWebNet-HA#11, OpenWebNet-HA#12, OpenWebNet-HA#13) are not typed as state replies, as dimension 7/30/31/32 writes already were. Builders: set_mode / set_temperature / set_timed_manual refuse three-digit probe-class addresses (001, 105, OpenWebNet-HA#105; ORACLE_CONFIRM H19) instead of rewriting them to michnovka#1 / OpenWebNet-HA#105, the fan builders raise "Invalid zone number" for them; turn_off("0", standalone=True) keeps the literal 0 (ORACLE_CONFIRM H3: *4*303*0## is nack on 12/12, #0 is the central unit); set_temperature raises for a temperature outside 5.0..40.0 (what.md "Zone setup commands", T 0050..0400) instead of clamping; set_holiday_end_date validates the calendar date (dimensions.md "DIMENSION 30 - holiday end"; ORACLE_CONFIRM H18: 31 February is nack on 12/12). #0#C builders log "central unit #0#C". Existing tests that pinned the old readings are updated; the new tests/test_who4_audit_fixes.py pins every row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Report 21 H11, H12.
README: set_central_mode takes CLIMATE_MODE_HEAT / CLIMATE_MODE_COOL /
CLIMATE_MODE_OFF ("heat" / "cool" / "off"), not "heating" / "cooling"
(the quick-start raised ValueError), and the feature list named three
builders that do not exist (set_central_antifreeze,
set_central_thermal_protection, set_central_off); it now lists the
central-unit builders that do.
docs/protocol_conformance_matrix.md, tests/golden/README.md and the
mcp_notes of golden fixture thermo.resp.probe.status.neg_temp (frame and
values unchanged; corpus.json regenerated with tools/golden/validate_corpus.py):
dimension 12 is the complete probe status, the setpoint after the local
offset in 0020..0430 (Encyclopedia who-4-temperature-control/dimensions.md,
"DIMENSION 12 - complete probe status"; ENCY_CONFIRM TC-H12), not a
measured temperature, and OWNd stores it in local_set_temperature, never
in _measured_temperature.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… (decisions D39, D40, D54, D67) Precedent gate on report 21 H1, H5, H6, H17, H19: MyHOME's pinned suite (v2-phase1-architecture, run as OWNd's myhome-conformance CI does) failed 44 tests on the worker's commit. The audit fixes are kept and the finer information stays in the new properties; the attribute values MyHOME asserts are restored: - H1: `#0#C` keeps `zone == C` (MyHOME `_zone_route_keys`, its `test_issue_431`, `test_climate_central_discovery`, `test_climate_routing_invariants`); `scope` `central`, `central_unit` and `unique_id` `4-#0#C` carry the central-unit identity. - H5: 102 / 202 / 302 keep `mode` `off` and the log line "mode is set to 'off'" (MyHOME `test_message_edges`, the F461 and MyHomeServer1 live traces, `test_issue_429`); `operating_mode` says antifreeze / thermal_protection / protection. - H6: x15 and x3DDD keep `mode` heat / cool / auto (MyHOME `test_message_edges`, `test_climate_holiday_program`); `operating_mode` says holiday / vacation. `CLIMATE_MODE_PROTECTION` / `CLIMATE_MODE_HOLIDAY` are dropped (no longer produced). - H17: the Dimension 14 write echo on the event path keeps its `target_temperature` type (MyHOME `test_legacy_platforms` replays `*michnovka#4*01*OpenWebNet-HA#14*0220*1##` through `OWNEvent.parse` and expects the target). - H19: `set_temperature` clamps to 5.0..40.0 again (MyHOME `test_heating.py::test_set_temperature_clamp_low/_high`); the probe-class address refusal stays. Evidence: Encyclopedia who-4-temperature-control/addressing.md and what.md as in the worker's commit; MyHOME pins per DECISIONS_REGISTER D39 (dual-OWNd tests), D40 (mode table), D54 (climate.accept), D67 (#845 replay). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ntral setpoint target documented (review follow-up) Review follow-up on report 21 H1, H2, H13, H19 (branch fix/who4-addressing-modes-dim19): - unique_id: the id is unchanged; the claim is corrected. Distinct per probe / actuator / central-unit class (`4-1` vs `4-001`, `4-0` vs `4-#0`, `4-#0#C`, `4-Z#N`, `4-3#Z#N`), shared per zone for the forms MyHOME routes on: `#Z` and `#Z#C` give the `4-Z` of the master probe `Z` (`*4*311*michnovka#1##` and `*4*311*1##` are both `4-1`, `michnovka#1#4#01` and `1#4#01` both `4-1#4#01`). CHANGELOG, the class and property docstrings and a new test say so (DECISIONS_REGISTER D39: MyHOME zone routing). - Builders refuse the actuator form `0#N` (actuator N of zone 0 in the Dimension 20 grammar, Encyclopedia addressing.md "Actuator forms"; SCOPE_ACTUATOR on the event side) with `ValueError("... '0#N' is actuator N of zone 0 ...")` instead of retargeting it to the 4-zone central unit `#0#N`; `#0#N` itself still builds. Test added. - `set_temperature("#0" / "#0#C" / 0)`: master (fb0acd7) already accepted them and built the central-unit setpoint `*michnovka#4*#0*OpenWebNet-HA#14*TTTT*M##`; kept (D5: central-unit setpoints have `set_central_temperature`, same frame) and documented in the docstring with what.md "Zone setup commands" (`michnovka#1..OpenWebNet-HA#99`). Test pins the equality with `set_central_temperature`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…nsion 14, stop reading unpublished Dimension 19 values as fan speeds Lab audit 2026-11-03, rows L-A1, L-A2, L-A3 (FIX_LAB_2026-11-03.md). L-A1: `*michnovka#4*Z*2*T##` / `*michnovka#4*Z*3*T##` decode as `protection_setpoint` (MESSAGE_TYPE_PROTECTION_SETPOINT, thermal protection, cooling side) and `antifreeze_setpoint` (MESSAGE_TYPE_ANTIFREEZE_SETPOINT), `who4_temperature` units, logs "Zone 2's thermal protection setpoint is 35.0°C." / "Zone 2's antifreeze setpoint is 7.0°C." (were the raw frame, no property). Neither dimension is in the published WHO 4 document (Encyclopedia who-4-temperature-control/dimensions.md lists no Dimension 2 or 3; Dimension 7 is the protection mode). Live F461 2.0.11, zone 2 KW4691 (reports/data/live_f418_0010_2026-11-03/lab_probe_output.txt): `*michnovka#4*2*2##` -> `*michnovka#4*2*2*0350##`, `*michnovka#4*2*3##` -> `*michnovka#4*2*3*0070##` (BTicino factory defaults 35.0 °C / 7.0 °C); lab_probe_writes_output.txt: `*michnovka#4*2*michnovka#2*0340##` and `*michnovka#4*2*michnovka#3*0060##` ACKed and read back, `*michnovka#4*2*michnovka#3*0999##` NACKed (read back 0070). Oracle suite lab-2026-11-03: `*michnovka#4*2*2##` and `*michnovka#4*2*3##` are forwarded to the bus (dimension bytes 0C / 0F) by 10 of the 12 emulated gateways (refused on 2; replayed: F450, F453, F453AV, F454, F459, F460, F461, H4684, MH200, MH200N, MH202, MyHomeServer1; only the MH202 ACKs, the emulated zone does not answer). No builders: reads and writes stay raw commands (D1). L-A2: a Dimension 14 reply outside the published `0050..0400` (dimensions.md "DIMENSION 14", page 18) keeps `set_temperature` (D1) but logs "... is set to 99.9°C (outside the published 5.0..40.0 °C range)."; the write echo on the event path is flagged the same way. Live F461 2.0.11: `*michnovka#4*2*OpenWebNet-HA#14*0999*3##` NACKed (read back 0230), `*michnovka#4*2*OpenWebNet-HA#14*0400*3##` applied, `*michnovka#4*2*OpenWebNet-HA#14*0049*3##` ACKed and stored as 0045 (4.5 °C, below the published bound), so such a reply can be a real value. Oracle: `*michnovka#4*2*OpenWebNet-HA#14*0999*3##` is `nack, silent` on 12 of 12; `*michnovka#4*2*OpenWebNet-HA#14*0200*3##` goes out on 7 of the 12 (4 ACK, 3 forward and NACK on the empty bus; 5 refuse every Dimension 14 write form). `OWNHeatingCommand.set_temperature` keeps clamping to 5.0..40.0 instead of raising (D20/D21 would raise, but the register pins the clamp: gate commit 33a8fff, CHANGELOG "MyHOME relies on the clamp", test_h19_set_temperature_clamps_out_of_range_values); the docstring records the live NACK / 4.5 evidence. L-A3: Dimension 19 CV / HV outside `0..8` and `14..16` (dimensions.md "DIMENSION 19") are not fan speeds: `*michnovka#4*1*19*9*0##` logs "Zone 1's cooling valve reports the unpublished valve value 9; heating valve is off." with is_cooling() None, cooling_fan_on / cooling_fan_speed None and is_active() None (was "cooling fan is on at speed 4", cooling_fan_on True); the heating side likewise; an active published side still makes the zone active. Published values and 14 / 15 / 16 are unchanged (live `*michnovka#4*2*19##` -> `*michnovka#4*2*19*5*0##`, fan-coil off; oracle: the read goes out on 12 of 12). Tests: tests/test_who4_lab_2026_11_03.py (OWNMessage.parse / OWNEvent.parse symmetry, D13). MyHOME v2-phase1-architecture suite against this branch: 5187 passed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Added c650aee + 7100cd2 (normal pushes): three rows from a spec-vs-code lab audit (2026-11-03), each confirmed on the live F461 2.0.11 test plant (zone 2, a KW4691) and replayed on the 12 emulated gateways (
Checks: pytest 2956 passed with 100 % statement and branch coverage on |
…addressing" Comment only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thanks for the detailed WHO 4 audit and for checking MyHOME against the branch.
Following our standing rule — if we don't have real, committed on-wire plant traces from physical hardware, we don't change behaviour — this PR currently bundles too many doc-driven and inferred runtime changes alongside valid crash-safety and documentation fixes.
What can stay
- Malformed / truncated dimension parser crash-guards: Preventing uncaught
IndexError/ValueErrorinOWNHeatingEventon empty or non-digit dimension tags (*#4*1*11*##,*#4*1*20*##,*#4*1*30*##,*#4*1*60*##, non-digitDIMENSION 12 / 13 / 14). - Pure documentation & golden metadata fixes: Updating
README.md(CLIMATE_MODE_*constants),docs/protocol_conformance_matrix.md§3.4,tests/golden/README.md, andtests/golden/frames/who04_thermo.yaml(mcp_notes) to clarify thatDIMENSION 12is complete probe status (setpoint after local offset) rather than measured temperature.
Blockers — Behaviour changes without committed plant traces (please revert)
| # | Symbol / File | Behaviour change without real plant trace | Required action |
|---|---|---|---|
| 1 | OWNHeatingEvent.unique_id & OWNHeatingEvent.actuator in OWNd/message/heating.py |
Changes unique_id across central units (#0#1: "4-1" -> "4-#0#1"), actuators (2#1: "4-2" -> "4-2#1", 0#2: "4-#0" -> "4-0#2"), general (0: "4-#0" -> "4-0"), and probes (001: "4-1" -> "4-001", 000: "4-#0" -> "4-000"), and changes actuator return type from str ("1") to int | None (1 / None), forcing edits to existing assertions in test_heating.py, test_message_protocol.py, test_issue_77_firmware_findings.py, and test_who4_actuators_status.py. |
Revert both unique_id and actuator to master — both are public API contracts. |
| 2 | OWNHeatingEvent DIMENSION 19 (14, 15, 16 and 9..13, 17+) in OWNd/message/heating.py |
Maps 14 / 15 / 16 to fan-coil OFF and drops 9..13, 17+ to None. As the PR description notes, 14 / 15 / 16 rest on Encyclopedia prose (ownkb:claim:c003275 / c003276, "information supplied by the MyHOME team") with zero committed on-wire plant traces in OWNd or MyHOME and zero firmware-oracle data. |
Revert DIMENSION 19 decoding changes until a real fan-coil bus capture showing 14 / 15 / 16 is committed. |
| 3 | DIMENSION 15 3-digit temperature (123 -> 12.3 °C) & DIMENSION 22 split-unit decoder in OWNd/message/heating.py |
Decodes 3-digit DIMENSION 15 temperatures (explicitly noted as "an inference", rewriting test_invalid_temperature_width_and_sign_return_none in tests/test_heating.py which pinned 3-digit values returning None) and adds DIMENSION 22 split_control decoding with zero physical split-unit captures. |
Revert both 3-digit DIMENSION 15 decoding and DIMENSION 22 split_control. |
| 4 | DIMENSION_WRITING message_type suppression, human_readable_log rewrites & speculative mode/address properties in OWNd/message/heating.py |
Strips message_type on DIMENSION_WRITING frames on the event path (except DIMENSION 14) and alters human_readable_log across manual/auto/holiday/vacation/program/scenario events, central units, DIMENSION 20 value 9 ("Auto" -> "fan-coil is on"), and out-of-range DIMENSION 14. |
Revert runtime message_type and human_readable_log changes on valid frames; keep address-class / mode distinctions as docstring documentation until needed by trace-backed MyHOME behaviour. |
| 5 | OWNHeatingCommand builder regressions (turn_off("0", standalone=True), rejected WHERE forms, and uncommitted DIMENSION 2 / 3 bench refs) in OWNd/message/heating.py |
turn_off("0", standalone=True) now emits *4*303*0## instead of *4*303*#0## — *4*303*0## is NACKed 12/12 in the firmware oracle, whereas *4*303*#0## is accepted! Zone builders also newly raise ValueError on 0#N, 001, 105, #105. Finally, DIMENSION 2 / 3 protection/antifreeze setpoint builders/decoders cite uncommitted local reports/data/live_f461_allprs_2026-11-04/ files. |
Revert the turn_off and WHERE-validation changes; hold back DIMENSION 2 / 3 builders/decoders until the raw KW4691 on-wire capture is committed as a repository fixture. |
… the heating parser guards and docs Maintainer review of OpenWebNet-HA#156: without committed on-wire plant traces from physical hardware, runtime behaviour is not changed. Spec PDFs, Encyclopedia prose, archived client code and empty-bus gateway ACKs are documentation context only; oracle crash findings justify crash guards. Blocker 1 (unique_id / actuator): OWNHeatingEvent.unique_id and .actuator reverted to master (#0#1 -> "4-1", 2#1 -> "4-2", 0#2 -> "4-#0", 0 -> "4-#0", 001 -> "4-1", 000 -> "4-#0"; actuator is the str "1" fallback again). The _Who4Address class, scope / zone_number / central_unit / probe_number / interface / address properties, the SCOPE_* and OPERATING_MODE_* constants and operating_mode were removed with it (no runtime effect allowed). Master's assertions restored in test_heating.py, test_message_protocol.py, test_issue_77_firmware_findings.py, test_who4_actuators_status.py, test_firmware_contract.py and the other touched test files. Blocker 2 (DIMENSION 19): the 14/15/16 fan-coil OFF mapping and the 9..13 / 17+ -> None decoding reverted to master. Blocker 3 (DIMENSION 15 / 22): the three-digit DIMENSION 15 decoding reverted (master's test_invalid_temperature_width_and_sign_return_none restored); the DIMENSION 22 split_control decoder, SPLIT_* constants and MESSAGE_TYPE_SPLIT_CONTROL removed. Blocker 4 (message_type / log lines): the DIMENSION_WRITING message_type suppression and every human_readable_log rewrite on valid frames (manual / auto / holiday / vacation / program / scenario, central units, DIMENSION 20 value 9, out-of-range DIMENSION 14, WHAT 40 / 3000 / 3100 / 3200) reverted to master. Blocker 5 (builders / Dimensions 2 and 3): turn_off("0", standalone=True) emits *4*303*#0## again; the ValueError rejections of 0#N, 001, 105, OpenWebNet-HA#105 and the calendar-date check of set_holiday_end_date reverted; the DIMENSION 2 / 3 protection and antifreeze setpoint decoders, their constants and exports, and tests/test_who4_lab_2026_11_03.py removed, with every citation of reports/data/.... Kept: the crash guards for empty or missing DIMENSION 11 / 13 / 60 values (*michnovka#4*1*11*##, *michnovka#4*1*60*##, *michnovka#4*1*11##, *michnovka#4*1*13##, *michnovka#4*1*60## raised ValueError / IndexError out of the parser; now untyped) and the documentation and golden metadata fixes (README CLIMATE_MODE_* quick-start and builder list, docs/protocol_conformance_matrix.md 3.4, tests/golden/README.md, who04_thermo.yaml / corpus.json mcp_notes: DIMENSION 12 is the setpoint after local offset). The CHANGELOG keeps only those two entries, worded as no behaviour change on valid frames. tests/test_who4_audit_fixes.py keeps the H9 guard tests, the master clamp tests and the MyHOME legacy-frame table with master's ids. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Applied in
Also reverted under the same rule, although not listed: the new read-only address properties ( Update (2026-11-04, 16:2xZ, Kept, as listed under "What can stay": the parser crash-guards for empty DIMENSION 11 / 13 / 60 values ( Committed plant evidence. None of the reverted decoders is backed by a committed trace: the MyHOME fixtures carry no DIMENSION 19 value 14..16, no 3-digit DIMENSION 15, no DIMENSION 22 event and no Update (2026-11-04, 16:07Z): New committed plant trace for the WHO 4 items here: MyHOME#865 ( |
… docstring (review: documentation only)
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thanks @gdluck for cleanly reverting all five runtime blockers from #5769893204 in 80af0dc..6e8c49c — the README.md CLIMATE_MODE_* / central-unit builder fixes, docs/protocol_conformance_matrix.md §3.4, and the golden DIMENSION 12 metadata note all match who-4-temperature-control/dimensions.md ("DIMENSION 12 - complete probe status").
As on #155, we ran one extra @fedem95-style verification pass while @fedem95 is away so this can merge cleanly once green. Five items came up (plus a quick rebase/merge with master now that #155 has landed):
1. Remaining uncaught ValueError crashes in OWNMessage.parse / OWNEvent.parse + vacuous test cases
Two grammar-accepted WHO 4 shapes still raise uncaught ValueError out of OWNMessage.parse / OWNEvent.parse / OWNHeatingEvent:
DIMENSION 15empty sensor slot (*#4*1*15**##,*#4*1*15**0215##,*#4*1*15#1**0215##):base.py's_DIMENSION_REQUEST_REPLY((?:\*\d*)+) parsesdimension_valueas["", ""]/["", "0215"](len >= 2), andOWNd/message/heating.py:673-674callsint(self._dimension_value[0])->ValueError: invalid literal for int() with base 10: ''.- Star
WHERE(*4*1**##,*4*303**##):base.py's_STATUSadmits(?P<where>\*|#?\d+)across allWHOs, soOWNMessage.parse("*4*1**##")dispatches_where = "*"toOWNHeatingEvent, whereOWNd/message/heating.py:339-342callsint("*")->ValueError: invalid literal for int() with base 10: '*'. - Why
tests/test_who4_audit_fixes.py:60-82missed them: the five*x##frames (*#4*1*12*x##,*#4*1*13*x##,*#4*1*14*x##,*#4*1*19*x*0##,*#4*1*20*x##) failbase.py's regex completely (is_valid is False,dimension is None), so none of thoseelif self._dimension == ...branches run andevent.message_typeisNonefor all 12 parametrized frames (leaving"local_offset","local_target_temperature","target_temperature"on line 82 dead). Please replace/supplement them with the grammar-accepted empty-tag forms throughOWNMessage.parse/OWNEvent.parse(*#4*1*12*##,*#4*1*13*##,*#4*1*14*##,*#4*1*15*##,*#4*1*15**##,*#4*1*15**0215##,*#4*1*19*##,*#4*1*19*5*##,*#4*1*19**5##,*4*1**##).
2. str.isdigit() Unicode digit leak in DIMENSION 11 (and 15 / 19 / 20 / 30 / 31 / 32) vs ASCII-only _WHO4_HUMIDITY_REGEX
- On
heating.py:52,DIMENSION 60usesre.ASCII(_WHO4_HUMIDITY_REGEX = re.compile(r"[0-9]+(?:\.[0-9]+)?", re.ASCII)), so*#4*1*60*45##fails closed (None). - On
heating.py:594, the newDIMENSION 11guard usesself._dimension_value[0].isdigit(). Becausebase.py's_DIMENSION_REQUEST_REPLYhas nore.ASCII,OWNMessage.parse("*#4*1*11*3*##")decodes fullwidth Unicode"3"asfan_speed == 3(and likewise inDIMENSION 15 / 19 / 20 / 30 / 31 / 32, e.g.*#4*1*19*5*0##,*#4*#0*30*01*02*2026##; plus"²".isdigit()isTruewhileint("²")raisesValueError). - Using an ASCII-only
[0-9]+check (matching_WHO1004_COUNT_REGEXin the same module and_int_fieldin#155) makes all WHO 4 integer dimension reads fail closed on non-ASCII wire bytes without changing any valid ASCII frame.
3. Three factual fixes in the OWNHeatingEvent docstring (OWNd/message/heating.py:316-332)
- Non-existent public
actuatorattribute: Line 326 states`zone`, `unique_id` and `actuator` keep their long-standing values for every form— onmasterand on this branch,OWNHeatingEventhas no publicactuatorproperty (hasattr(OWNHeatingEvent("*#4*2#1*20*1##"), "actuator")isFalse; only privateself._actuatorexists onDIMENSION 20). - Dimension-write
message_typeclaim: Lines 330–331 state "a dimension write frame keeps the samemessage_typeas the matching read."OWNMessage.parseroutes dimension writes toOWNHeatingCommand(nomessage_type), and onOWNEvent.parse/OWNHeatingEvent,DIMENSION 7explicitly checksself._message_type != "DIMENSION_WRITING"(line 571:*#4*1*#7*1*1*0200##hasmessage_type is Nonevs read"zone_state"), andDIMENSION 30 / 31 / 32also skipDIMENSION_WRITING. who-4-temperature-control/addressing.mdattribution:"Published functional WHERE forms"defines0(all probes),1..99(master probe of zone1..99, including in central-unit plants — not only "a stand-alone zone probe"), and001..099(all probes in zone — omitted here even though1vs001is the page's primary contrast), alongsidePZZ,#0, and#1..#99;#0#C,#Z#C, and external probex00(1..9) come from"MyHOME_Suite address rules"and"Historical central-unit variants", not the publishedWHEREtable.
4. Remove private audit references (report 21, H9, H11, H12) & CHANGELOG.md section
CHANGELOG.md:8-9still cites; report 21 H9,; report 21 H11, and; report 21 H12(and baredimensions.md "DIMENSION 12"instead ofwho-4-temperature-control/dimensions.md"DIMENSION 12 - complete probe status"), and lists the parser crash fix under### Changedrather than### Fixed(note#155just merged tomaster, so rebasing/mergingmasterwill resolve theCHANGELOG.md/README.mdconflict at the same time).tests/test_who4_audit_fixes.pystill has(report 21, row H9)(line 1),# --- H9: ...(line 41), andtest_h9_*function names.
5. Update the PR Title and PR Body to match the actual diff
- Both the PR title (
fix(heating): WHO 4 WHERE address classes, operating_mode, dimension 19 OFF states, parse robustness, builder validation (Encyclopedia audit, group 1)) and the PR body sections below the follow-up table (## Summary,## Changes,## State Diagram,## Also in this diffreferencingDimension 2 / 3,tests/test_who4_lab_2026_11_03.py,L-A1..L-A3,H1..H19,decisions D39..D67) still describe the reverted features and files rather than what this PR now ships.
…-modes-dim19 # Conflicts: # CHANGELOG.md
…t and wildcard WHERE no longer raise; docstring and test wording Review 5818563137 on OpenWebNet-HA#156, five items (the master merge with OpenWebNet-HA#155 is the commit before): 1. Two grammar-accepted shapes still raised ValueError out of OWNMessage.parse / OWNEvent.parse: an empty DIMENSION 15 sensor slot (`*michnovka#4*1*15**##`, `*michnovka#4*1*15**0215##`, `*michnovka#4*1*15#1**0215##`: int('') on the slot) and the wildcard WHERE `*4*1**##` / `*4*303**##` (int('*') for the zone). The slot is read with `_ascii_int` (None = no reading, frame typed as master typed DIMENSION 15 with every value None); a WHERE that is not digits gives zone 0, as a frame without a WHERE already did. The five `*michnovka#4*1*12*x##`-style test frames fail the grammar entirely (is_valid False, dimension None), so that test asserted nothing about the decoders; it is replaced by the grammar-accepted empty-tag forms through both parse entry points, plus the wildcard WHERE cases, and one test that records why the rejected frames are not a crash test. 2. `str.isdigit()` is true for fullwidth digits and base.py's `\d` lets them through: `*michnovka#4*1*11*3*##` decoded fan_speed 3, `*michnovka#4*#0*30*01*02*2026##` a date. DIMENSION 11, 15, 19, 20, 30, 31 and 32 now read their integer fields through `_WHO4_INT_REGEX` (`[0-9]+`, re.ASCII), as the DIMENSION 60 humidity value already did; a non-ASCII field leaves the frame untyped with the raw log. No ASCII frame changes. 3. OWNHeatingEvent docstring: no public `actuator` attribute is claimed (the number is private to the DIMENSION 20 log); a dimension write goes to OWNHeatingCommand via OWNMessage.parse and stays untyped via OWNEvent.parse (DIMENSION 7 / 30 / 31 / 32 skip DIMENSION_WRITING), as on master; the WHERE forms are attributed to the right sections of who-4-temperature-control/addressing.md ("Published functional WHERE forms": 0, 1..99 master probe in any plant, 001..099 all probes of a zone, PZZ, #0, michnovka#1..OpenWebNet-HA#99; "Actuator addressing": Z#N; "MyHOME_Suite address rules" / "Historical central-unit variants": #0#C, #Z#C, N00). 4. The audit-row references (report 21, H9, H11, H12) are gone from the test module and, in the merge commit, from CHANGELOG.md, where the crash guard now sits under Fixed and the DIMENSION 12 note cites "DIMENSION 12 - complete probe status". 5. PR title and body rewritten to what ships (on GitHub). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks. All five are in 887a5ad, on top of a merge with
Run here (Python 3.14, Generated by Claude Code |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
LGTM — all five items from #5818563137 (DIMENSION 15 empty sensor slot & wildcard WHERE crash guards, _WHO4_INT_REGEX ASCII-only integer fields across DIMENSION 11 / 15 / 19 / 20 / 30 / 31 / 32, OWNHeatingEvent docstring accuracy and who-4-temperature-control/addressing.md attribution, CHANGELOG.md / test cleanup, and PR title/description update) are cleanly resolved in 887a5ad. Thanks @gdluck!
…dimension 52 guard allows only the tested forms; 3488 and oracle provenance; wording Review 5819194700 on OpenWebNet-HA#157, five items (the merge with master, OpenWebNet-HA#155 and OpenWebNet-HA#156, is the commit before): 1. AsyncSerialTransport.send applied only the WHO 4 dimension 22 guard, so `*OpenWebNet-HA#18*51*52#2026#10##` (with or without `##`) was written to the serial port. command_session.destructive_frame_reason() is now the one classifier behind OWNCommandSession.send, send_to_gateway and AsyncSerialTransport.send (AsyncTcpTransport.send goes through the session), returning the reason or None. tests/test_who18_dimension52_guard.py mirrors tests/test_who4_dimension22_guard.py: safe forms written through all four entry points, refusals across all four (serial with and without `##`), connect.assert_not_awaited() for the session and the one-shot helper, single serialisation, buffers and concatenated chunks; the test that never sent is gone. tests/typing/consumer_send.py exercises both guards. 2. The regex `52#[0-9]{4}#` was the complement of two crash rows, not the tested forms: `52#02026#10` and `52#256#10` reach the same one-byte atoi() formatter, and `*OpenWebNet-HA#18*`, `*052`, `OpenWebNet-HA#10`, `*OpenWebNet-HA#52` writes, extra `#` parameters, an empty WHERE, fullwidth digits and multi-value replies all parsed as valid and passed the guard. is_who18_dimension_52 / is_who18_dimension_52_crash_form follow heating.py's two-step pattern (detect any DIMENSION 52 chunk after latin-1 decoding; allow only `*OpenWebNet-HA#18*W*52`, `*OpenWebNet-HA#18*W*52*Wh`, `*OpenWebNet-HA#18*W*52#YY#M`, `*OpenWebNet-HA#18*W*52#YY#M*Wh` with ASCII YY 0..99 and M 1..12, `\Z` anchors). is_who18_dimension_52_four_digit_year stays as an alias. 3. The 3488 030205 (HOMETOUCH) crash rows are cited with the F460 and F461 ones, with the oracle provenance (repository, suite ownd-audit-frames, index SHA-256, emulated firmware caveat), in energy.py, the reason string and CHANGELOG. 4. README and scripts/sync_documentation.py no longer list "Energy Management actuators"; the Stop&Go docstrings say 11..19 is master's range from the oracle energy-ts10 suite (no physical Stop&Go trace is committed); _refuse_destructive_frame's docstring, the zigbee-variant.md heading ("DIMENSION table") and the F454 firmware (2.0.51) are corrected. 5. Audit tags (report 21, E1..E12, decision D1) removed from the tests and golden notes; the CHANGELOG entry sits under Fixed (merge commit); PR title and body are rewritten on GitHub. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Requested by A
Summary
WHO 4 checked against the Encyclopedia functional pages (
who-4-temperature-control/{addressing,dimensions,what}.md). What ships is small: the WHO 4 event decoder no longer raises out ofOWNMessage.parse/OWNEvent.parseon frames the grammar accepts but it could not read, its integer fields are read only when they are ASCII digits, and three documentation errors are corrected. No decoded value, log line or accepted builder domain differs frommasterfor any well-formed ASCII frame; the address-class, operating-mode, DIMENSION 15 / 19 / 22 and builder-domain findings of the audit stay documentation only (no committed plant trace carries them) and are described in theOWNHeatingEventclass docstring.Changes
OWNd/message/heating.py):OWNMessage.parse/OWNEvent.parsereturn an untyped event instead of raising for an empty DIMENSION 11 / 13 / 60 value (*#4*1*11*##,*#4*1*60*##:ValueError;*#4*1*11##,*#4*1*13##,*#4*1*60##:IndexError), an empty DIMENSION 15 sensor slot (*#4*1*15**##,*#4*1*15**0215##,*#4*1*15#1**0215##:ValueErrorfromint('')) and the wildcard WHERE (*4*1**##,*4*303**##:ValueErrorfromint('*'); the event is zone 0, as a frame without a WHERE already was). The integer fields of DIMENSION 11, 15, 19, 20, 30, 31 and 32 are read only when they are ASCII digits (_WHO4_INT_REGEX, like the DIMENSION 60 humidity value):*#4*1*11*3*##decodedfan_speed3 before, it is untyped now.mode="heating"/"cooling"replaced byCLIMATE_MODE_HEAT/CLIMATE_MODE_COOL, whichset_central_modeaccepts; the feature list names the builders that exist (set_central_program,set_central_scenario,set_central_weekend,set_central_holiday,set_holiday_end_date,set_holiday_end_time) instead of three that do not.docs/protocol_conformance_matrix.md§3.4,tests/golden/README.mdand one goldenmcp_notes: DIMENSION 12 is the setpoint after the local offset, stored inlocal_set_temperature, not a measured temperature (who-4-temperature-control/dimensions.md"DIMENSION 12 - complete probe status"); DIMENSION 20 value 9 is "fan-coil is on". Frames unchanged.OWNHeatingEventdocstring: the published WHERE forms (0,1..99master probe,001..099all probes of a zone,PZZ,#0,#1..#99),Z#Nfrom "Actuator addressing",#0#C/#Z#C/N00from the MyHOME_Suite and historical sections;zone/unique_idunchanged for every form; dimension writes go toOWNHeatingCommandand stay untyped on the event path, as onmaster.Tests:
tests/test_who4_audit_fixes.py(empty-tag forms through both parse entry points, wildcard WHERE, fullwidth digits for every integer dimension, the ASCII frames MyHOME's traces rely on, theset_temperatureclamp MyHOME pins). Full suite,ruff check,mypy OWNd tests/typing,scripts/verify_coverage.py(100 % statements and branches onheating.py),scripts/sync_documentation.py --checkandscripts/verify_library_standards.pypass. Branch merged withmasterafter #155.🤖 Generated with Claude Code