Skip to content

fix(heating): set_central_mode requires a central-unit address - #137

Draft
gdluck wants to merge 1 commit into
OpenWebNet-HA:masterfrom
gdluck:fix/who4-set-central-mode-address
Draft

gdluck wants to merge 1 commit into
OpenWebNet-HA:masterfrom
gdluck:fix/who4-set-central-mode-address

Conversation

@gdluck

@gdluck gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Requested by A

Summary

OWNHeatingCommand.set_central_mode("1", CLIMATE_MODE_AUTO) built *4*311*1##: the builder accepted any where. The central-unit WHATs (311 automatic, 1 heating, 303 off, ...) address the central unit by API contract: #77 fix 3 ("*4*311*1## produces no bus frame; *4*311*#1## does") and the WHO 4 specification, where the central-unit commands take #0 (99-zone central unit), #0#N (zone N of a 4-zone central unit) or #Z (a zone addressed through the central unit). The zone-level builder for a plain zone is set_mode(where, ...).

The guard therefore applies to every WHAT this builder produces, by contract. The oracle evidence per WHAT is uneven and is listed honestly below: the plain form of 311 is nack/silent on all 12 emulated gateways and NACKed by a live F461 2.0.11, while the plain forms of 1 and 303 are forwarded to the bus by some gateways (as a frame addressed to zone 1, which is not what the central-unit builder is for).

Refs #77. The central_status deprecation-text correction that this PR carried at first is now its own PR (see Companion).

Changes

  • OWNd/message/heating.py: _WHO4_CENTRAL_WHERE_REGEX = #(?:0#[0-9]+|[0-9]+); set_central_mode requires str(where) to fullmatch it (#0, #0#N, #Z; ASCII digits, so #0\n and #١ are refused). #Z#N is not a central-unit form and is refused too (the first version of this PR accepted it). Anything else raises ValueError("Central unit mode needs a central unit address such as '#0': ..."). The default #0 is unchanged. Callers checked: MyHOME climate.py passes self._where, which is #0 or #0#1 when _central; the golden corpus uses #0 and #0#1.
  • CHANGELOG.md: entry under [Unreleased] / Fixed.

State Diagram

stateDiagram-v2
    direction TB

    state "1. OWNHeatingCommand.set_central_mode(where, mode)" as Builder {
        [*] --> Validate
        Validate --> Built : where fullmatch #0 | #0#N | #Z\n*4*311*#0##, *4*1*#0#1##, *4*303*#23##
        Validate --> Refused : plain zone 1 / 2 / 0, 23#1, #1#2, #, ##0, #0*22, #a, #0\n, #١\nValueError , before - *4*311*1## was built and sent
    }

    state "2. What the gateways do with the two forms of 311 (oracle thermo-zone-mode, ownd-pr82-heating; live F461 2.0.11)" as Gateway {
        [*] --> Plain
        Plain --> NackSilent : *4*311*1## nack/silent on 12 of 12\nlive *4*311*2## NACK
        [*] --> Central
        Central --> Forwarded : *4*311*#1## ack/out on 9, nack/out on F460 / F461 / MyHomeServer1\nlive *4*311*#2## ACK , no central unit - changes nothing
    }
Loading

Where are the traces

Subsystem Capture Plant Frames verified by this PR
WHO 4 central-unit mode, plain vs #Z form upstream MyHOME fixture tests/fixtures/traces/f461_live_2026-10-15 (merged), runs run3_climate and probe_followup BTicino F461 2.0.11, standalone zone 2, no central unit *4*311*2## NACKed; *4*311*#2##, *4*303*#2##, *4*302*#2##, *4*102*#2##, *4*202*#2## ACKed (they change nothing on a plant without a central unit)
WHO 4 central-unit reads on plants without a central unit trace PR: OpenWebNet-HA/MyHOME#799 (open), f461_live_2026-10-23 run 1 (run1_oracle_replay_readonly) and myhomeserver1_live_2026-10-24 run 2 (run2_reads_thermo_sound_alarm_lights) F461 2.0.11; MyHomeServer1 3.87.13 (*#13**16*3*87*13##) F461: *#4*#2##, *#4*#2*14##, *#4*#0##, *#4*#0*14## NACK after the bus timeout. MyHomeServer1: *#4*#0## and *#4*#0*14## NACK after ~4.2 s. No *4*311*...## was sent on the MyHomeServer1 (a real home).

What does the Oracle say

Suites thermo-zone-mode and ownd-pr82-heating (results/<P>/<V>/oracle/full/<suite>.tsv; the rows are identical in both where both carry the frame). Twelve gateways: F450 020010, F453 010100, F453AV 030014, F454 020051, F459 020105, F460 020012, F461 020011, H4684 020054, MH200 020102, MH200N 010108, MH202 010024, MyHomeServer1 028206.

Frame ack / out nack / out (forwarded, no central unit on the emulated bus) nack / silent
*4*311*1## (plain, this builder before the fix) none none all 12
*4*311*#1## (central form, this builder) F450, F453, F453AV, F454, F459, H4684, MH200, MH200N, MH202 (9) F460, F461, MyHomeServer1 none
*4*1*1## (plain heating) F450, F453, F453AV, F454, F459, MH200N, MH202 (7) F460, F461, MyHomeServer1 H4684, MH200
*4*0*1## (plain conditioning) the same 7 F460, F461, MyHomeServer1 H4684, MH200
*4*303*1## (plain off) F450, F454, F459, MH202 (4) F460, F461, MyHomeServer1 F453, F453AV, H4684, MH200, MH200N

So "every gateway refuses the plain form" holds for 311 only. For 1, 0 and 303 the plain form is a zone command that some gateways forward; this builder refuses it anyway because it is the central-unit builder (contract above), and set_mode(where, ...) remains the zone-level builder. No published row exists for the plain forms of 302, 102 and 202 (the live F461 ACKs their #2 forms, table above).

Testing

  • tests/test_who4_central_mode_address.py (new, 21 cases): test_set_central_mode_requires_a_central_address (14: 1, 2, 0, 23#1, empty, #, ##0, #0*22, #a, #0\n, an Arabic-Indic digit, #1#2, #00#1, #0#), test_set_central_mode_refuses_a_plain_zone_for_every_mode (3: 311, 1, 303 on where="1"), test_set_central_mode_keeps_central_addresses (#0, #0#1, #23), test_set_central_mode_default_is_the_central_unit.
  • Fail before / pass after: on master 17 fail, 4 pass (set_central_mode('1', 'auto') returns *4*311*1##, #1#2 is accepted); on this branch all 21 pass.
  • Full suite: 1928 passed, 1 skipped (master: 1907 passed, 1 skipped); 100 % statement coverage (scripts/verify_coverage.py), mypy OWNd tests/typing clean, ruff check clean, scripts/sync_documentation.py --check and scripts/verify_library_standards.py pass.

Live retest (2026-10-24)

Not exercised live: neither plant has a central unit and *4*311*...## was not sent (the plant's zone 2 is standalone; set_central_mode('2', 'auto') now raises without a gateway).

Companion

MyHOME: none needed (MyHOME already passes a central-unit address). Related OWNd PR: docs(heating): central_status deprecation text names the real reason (split out of this PR): #143.

🤖 Generated with Claude Code

@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!

@gdluck

gdluck commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@GreenGrassBlueOcean all the PR's where tripple checked with oracle, test plant - tested with fable and co-verified with kimi3-pro. If anything, let me know

@GreenGrassBlueOcean

GreenGrassBlueOcean commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Thanks @gdluck ,can you please split out the trace pr so that we can add the actual physical trace to the myhome repo? Like we did with all traces before?

@gdluck

gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Done: the physical capture is split out as OpenWebNet-HA/MyHOME#799 (tests/fixtures/traces/f461_live_2026-10-23/, derived_trace_archive JSON + README + 21 replay tests, synthetic MAC/IP, green on v2-phase1-architecture with plain OWNd master). It carries the 2026-10-23 F461 2.0.11 rows behind #136–#142 and OpenWebNet-HA/MyHOME#791–#798; the *4*311*2## NACK / *4*311*#2## ACK pair cited here is already upstream in f461_live_2026-10-15, so #799 references it instead of duplicating it.

@gdluck
gdluck marked this pull request as draft October 24, 2026 13:39
gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 24, 2026
…penWebNet-HA#136-OpenWebNet-HA#142 and MyHOME OpenWebNet-HA#791-OpenWebNet-HA#798

The maintainer asked on OpenWebNet-HA/OWNd#137 (comment 6384484511) for the
physical trace behind the 2026-10-24 pull requests to be added to this repo
like every capture before. Same plant and format as f461_live_2026-10-15 and
f461_live_2026-10-18: a BTicino F461 2.0.11 (WHO 13 device type 200, WHO 1013
134.15.5.0) with BT-F411/4 lights at 06 / 07, an F422 light at 09 whose
actuator lacked configurator 2 at capture time, a BT-F411/2 5 s shutter at 05
and a KW4691 standalone thermostat at zone 2 with a BT-F430/2 valve and a
BT-F430/4 fan coil; no central unit.

tests/fixtures/traces/f461_live_2026-10-23/: the derived trace archive (299
event-session frames, 939 tx / rx / result rows in ten runs: the oracle replay
of 165 read-only requests, reviewer reads, setpoint restore, final state,
report-form writes, the event session during a local Home Assistant run, the
bus monitor of an earlier run, two flicker re-tries) and its README with the
plant, the runs, what the plant established and which pull request each frame
backs. MAC / IP are synthetic in both their text and WHO 13 byte forms.

tests/test_f461_live_2026_10_23_trace.py pins the facts the pull requests rely
on: the *michnovka#4*2## reply order with dimension 12 before and different from 14,
*michnovka#4*2*20## followed by two actuator frames and *michnovka#4*#0*20## answering both,
*#1*0007## NACK vs *#1*07## ACK vs *#1*7## empty, *OpenWebNet-HA#13**0## echoed on the
event session, every WHO 16 request refused in under 10 ms, the dimension 27
model name frame, *michnovka#4*#2*14## and the other central-unit forms refused, one
*2*0*0##, the shutter's own stop 5.1-5.9 s after each run; plus entity replays
through handle_event that pass on the base branch with the pinned OWNd.

tests/test_trace_oracle_validation.py: tier 4 tolerates the authentic
*OpenWebNet-HA#13**27*70#52#54#49## reply until the pinned OWNd carries the dimension 27
decoder (OWNd#139); it parses to None without raising. The *4*311*2## NACK /
*4*311*#2## ACK pair cited by OWNd#137 is already in f461_live_2026-10-15 and
is referenced, not duplicated. scripts/update_trace_matrix.py leaves the
matrix unchanged (the F461 row already covers WHO 1, 2, 4, 13, 1013).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Central-unit WHATs (311, 1, 303, ...) address the central unit by API
contract (OWNd#77 fix 3): `where` must be `#0`, `#0#N` or `#Z`; `#Z#N`
and plain zones are refused with ValueError. The plain zone form
`*4*311*Z##` is nack/silent on all 12 emulated gateways (own-firmware-
oracle thermo-zone-mode, ownd-pr82-heating) and a live F461 2.0.11 NACKs
`*4*311*2##` while it ACKs `*4*311*michnovka#2##`. The default `#0` is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/who4-set-central-mode-address branch from 844f4fb to a6c4cdc Compare October 24, 2026 15:44
@gdluck gdluck changed the title fix(heating): set_central_mode requires a central-unit address; correct the central_status deprecation text fix(heating): set_central_mode requires a central-unit address Oct 24, 2026

This branch has not been deployed

No deployments
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