Skip to content

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

Merged
GreenGrassBlueOcean merged 1 commit into
OpenWebNet-HA:masterfrom
gdluck:fix/who4-set-central-mode-address
Oct 28, 2026
Merged

GreenGrassBlueOcean merged 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 addressing of the central-unit commands: the Legrand WHO 4 document (openwebnet-who-4.pdf, WHERE table p. 6, commands pp. 8-12 and 24-27) has #0 (99-zone central unit) and #1..#99 (a zone set up by the central unit); the 4-zone forms #0#C and #Z#C are not in that document and come from MyHOME_Suite OPEN.db / MHCatalogue 4695 and the firmware oracle (thermo-central-mode, thermo-ts10). The mode codes themselves are the ones BTicino's own client sends (libqtdevices): the PDF lists 303 and 102 on #0 (pp. 24, 27) and 311 / 302 on #Z only (pp. 9, 12), and has no *4*1*#0## / *4*0*#0## command, so this builder follows the client, not the document, for 1 / 0 / 311 / 302 on #0. 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[1-9]|[1-9][0-9]?))? (re.ASCII): the central unit only, #0 (3550, 99 zones) or #0#C with C = 1..99 / 01..99 (4695, the unit's configured zone), as MyHOME and the golden corpus use. #Z and #Z#C are zone / probe addresses (ControlledProbeDevice, bus command byte C2) and belong to set_mode(...), which already builds *4*311*#23## and *4*311*#23#1##; set_central_mode refuses them with a ValueError that names set_mode. One shared _validate_central_where(where, purpose) (strip, fullmatch, quoted error) is the first statement of all nine central-unit builders: set_central_mode, set_central_temperature, set_central_program, set_central_scenario, set_central_weekend, set_central_holiday, set_holiday_end_date, set_holiday_end_time, set_timed_manual_end_time; signatures and frames are unchanged, each now refuses a bare 1, #1, #23#1, #00, #0#0, #0#100, None, True and accepts #0, #0#1, #0#12, #0#05. History: squashed into one commit on master 4213cab (938b98b) with the authorship reset, as asked in review 5684988147; the earlier revisions (e8e20fb, 406d155, 2583a43) are folded into it.
  • 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#C | #Z | #Z#C, C and Z 1..99 or 01..99\n*4*311*#0##, *4*1*#0#1##, *4*311*#0#12##, *4*303*#23##, *4*311*#23#1##
        Validate --> Refused : plain zone 1 / 2 / 0, 23#1, #, ##0, #0*22, #a, #١, #00, #0#0, #999, #0#100\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 (merged), 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).

Revision 938b98b (review 5684988147; squashed on master 4213cab). Contract narrowed to #0 / #0#C; shared _validate_central_where across the nine central-unit builders; tests/test_who4_central_mode_address.py rewritten (the parity test became "set_central_mode refuses what set_mode builds", the reversed #Z#C comment is gone) and tests/test_who4_central_unit_where.py added (every builder, 120 tests); tests/test_who4_dimension22_guard.py heating fixtures use #0#4 instead of the no-longer-valid #4. Suite 2302 passed, 100 % statements, ruff / mypy / golden corpus / library standards / docs check clean; MyHOME v2-phase1-architecture suite against this head 5044 passed. One caveat for MyHOME found while checking callers: climate.py calls set_central_mode / set_central_temperature only under self._central, which yaml central: true can force on a non-central WHERE (where: "2"); such a config now raises instead of building *#4*2*#14*…## (bus-discovered devices are central only for #0 / #0#1). No MyHOME test covers that path; not changed here. Rebased onto master 4213cab as a single re-authored commit.

Testing

  • tests/test_who4_central_mode_address.py (new, 49 cases): test_set_central_mode_requires_a_central_address (28: 1, 2, 0, 23#1, empty, blank, #, ##0, #0*22, #a, #0\n1, an Arabic-Indic digit, #00#1, #0#, #00, #100, #999, #0#0, #0#00, #0#100, #0#999, #1#0, #1#00, #1#100, #1#2#3, None, True, -1), 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 (16, frame and log line: #0, #0#1, #0#4, #0#5, #0#05, #0#12, #0#99, #1, #01, #23, #99, #23#1, #23#12, #1#2, padded #0, #0 with a trailing newline), test_set_central_mode_agrees_with_set_mode_on_probe_addresses (set_mode("23#1") == set_central_mode("#23#1")), 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 49 pass.
  • Full suite (on master 6c3930d after fix(connection): refuse WHO 4 dimension 22 in every send path #136 / fix(profiles): alias H/L4684 & HC4684 to H4684Profile and enable WHO 22 on FW >= 6.0.0 #147, head 406d155): 2088 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 (closed).

🤖 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>
@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
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck please rebase

@gdluck
gdluck force-pushed the fix/who4-set-central-mode-address branch from a6c4cdc to 0b29827 Compare October 26, 2026 08:56
@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on master 87cafc3 (after #136): the CHANGELOG [Unreleased] entry and the heating.py regex block now sit beside the merged dimension-22 guard; content unchanged, suite and 100 % coverage green.

@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Self-correction in e8e20fb (no review asked for it). @GreenGrassBlueOcean could you confirm the reading below, or say if you see it differently?

Context. This PR made set_central_mode refuse plain zone addresses because *4*311*Z## is nack/silent on all 12 emulated gateways and on a live F461. The first revision also refused #Z#N and its docstring said "#Z#N is not a central-unit form".

How it was found. An adversarial read of the PR against its neighbours in OWNd/message/heating.py, not against its own claims: set_mode("23#1", "auto") in the same file builds exactly *4*311*#23#1##, _zone_address_and_name documents that form as what libqtdevices sends, and the firmware oracle thermo-ts10 suite shows all 12 gateways forward *4*311*#23#1## (9 ack/out, 3 nack/out, the same split as the *4*311*#1## the PR accepts). So the guard refused a frame the library itself builds. The same read showed the regex was unbounded (#999, #0#99, #01 built frames) while the sibling _WHO4_WHERE_REGEX of #140 is bounded, and that where was not stripped while actuators_status strips.

Live check (F461 2.0.11, 2026-10-26 16:45 UTC). *4*311*#2#1## is ACKed in 0.10 s with no bus event and zone 2 unchanged (a standalone zone, no central unit to act on), so the gateway accepts the form the fix now builds.

What changed. _WHO4_CENTRAL_WHERE_REGEX is #(?:0(?:#[1-4])?|[1-9][0-9]?(?:#[1-9])?) (#0, #0#1..4, #1..#99, #Z#1..9); where is stripped and quoted in the error; 41 test cases (was 21) including a parity test set_mode("23#1") == set_central_mode("#23#1"). Full suite 2080 passed on master 6c3930d, ruff and mypy clean. PR body and state diagram updated.

@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Correction in 406d155, following @GreenGrassBlueOcean's #148 review: the previous revision bounded #0#C to 1..4 and #Z#C to 1..9, reading #0#N as "zone N of a 4-zone central unit". Per MyHOME_Suite OPEN.db (#0#[ZA][ZB]), MHCatalogue 4695 (ZAZB_CENTRAL 1..99), BtExperience (parseControlUnit4 / parseZone4) and the oracle thermo-central-mode rows (*4*0*#0#2## → D1 02 …), #0#C is the 4-zone unit's configured zone selector and #Z#C the zone controlled through central unit C, both 1..99 (01..99). The regex is now #(?:0(?:#(?:0[1-9]|[1-9][0-9]?))?|(?:0[1-9]|[1-9][0-9]?)(?:#(?:0[1-9]|[1-9][0-9]?))?): #0, #0#5, #0#05, #0#99, #01, #23#12 build; only #00, #0#0, #0#00 and three-digit values are refused. 49 test cases, suite 2088 passed on master 6c3930d, ruff / mypy clean; body and diagram updated.

@GreenGrassBlueOcean GreenGrassBlueOcean left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking set_central_mode("1", ...) from emitting *4*311*1## (nack/silent across all 12 emulated gateways and on live F461 2.0.11) makes sense as public builder hardening, even though MyHOME (climate.py) already only calls set_central_mode when self._central is true (#0 or #0#C).

However, the current contract in _WHO4_CENTRAL_WHERE_REGEX has two architectural issues in OWNd/message/heating.py:

1. #Z and #Z#C are zone/probe addresses (ControlledProbeDevice), not Central Unit addresses (ThermalDevice)

  • In libqtdevices (TS10_1_0_23) and own-firmware-oracle (findings/MyHomeServer1/thermo-ts10.md), #0 (3550) and #0#C (4695) emit Central Unit bus telegrams ($06D1..0302C1..., command byte C1), whereas #23 and #23#1 emit zone bus telegrams ($06D1010302C2170000\r, command byte C2, zone 0x17 = 23 under central unit 1).
  • OWNHeatingCommand.set_mode already owns zone modes on central-linked probes (set_mode(23, CLIMATE_MODE_AUTO) -> *4*311*#23## and set_mode("23#1", CLIMATE_MODE_AUTO) -> *4*311*#23#1##, logging "Setting zone 23 mode to 'auto'.").
  • Allowing #Z and #Z#C in set_central_mode introduces two problems:
    1. set_central_mode("#23#1", CLIMATE_MODE_HEAT) builds *4*1*#23#1## (Central Unit winter season WHAT=1 on a probe address, which ControlledProbeDevice never sends).
    2. human_readable_log emits "Setting Central Unit #23#1 mode to 'auto' (code 311)." when #23#1 is zone 23, not a central unit. (Note also that tests/test_who4_central_mode_address.py:74 has the fields reversed as "#Z#C: probe C of zone Z" — in #23#1, 23 is the zone/probe and 1 is the central unit, matching _zone_number.)
  • set_central_mode is explicitly documented as "Set operation mode for Central Unit (3550 99-zone or 4695 4-zone)". Its valid where contract should be #0 or #0#C (C in 1..99 / 01..99, matching MyHOME and the golden corpus); callers addressing a central-linked zone #Z or #Z#C should use set_mode(...).

2. Inconsistency with the sibling Central Unit builders directly below (OWNd/message/heating.py)

  • set_central_temperature, set_central_program (*4*31PP*WHERE##), set_central_scenario (*4*32SS*WHERE##), set_central_weekend (*4*315#31PP*WHERE##), set_central_holiday (*4*33DDD#31PP*WHERE##), set_holiday_end_date (*#4*WHERE*#30*...##), set_holiday_end_time (*#4*WHERE*#31*...##), and set_timed_manual_end_time (*#4*WHERE*#32*...##) all default to where: str = "#0", log "Setting Central Unit {where} ...", and still accept bare where="1" without # (*4*3101*1##, *4*3201*1##, etc.).
  • If we enforce central-unit where validation on OWNHeatingCommand, let's share a single _validate_central_where(where) helper across the central-unit builders rather than guarding set_central_mode in isolation.

3. Housekeeping

  • Please update the PR body references (OpenWebNet-HA/MyHOME#799 is merged; #143 is closed).
  • Please rebase cleanly onto master (12fef0c8) without merge commit c82d88f and reset commit authorship on e8e20fb / 406d155 (Mac Book <macbook@Macs-MacBook-Pro.local>).

gdluck pushed a commit to gdluck/OWNd that referenced this pull request Oct 28, 2026
…review of OpenWebNet-HA#137)

`set_central_mode`'s `where` is the central unit itself: `#0` (3550,
99 zones) or `#0#C` with `C` 1..99 / 01..99 (4695), the forms MyHOME and
the golden corpus use. `#Z` and `#Z#C` are zone / probe addresses
(libqtdevices `ControlledProbeDevice`, bus command byte `C2`) that
`set_mode` builds, so the builder now refuses them with a `ValueError`
that names `set_mode`; it used to build `*4*1*OpenWebNet-HA#23#1##` and log
"Setting Central Unit OpenWebNet-HA#23#1" for zone 23 under central unit 1.

The guard is one shared `_validate_central_where(where, purpose)` in
`OWNd/message/heating.py` (strip, fullmatch
`#0(?:#(?:0[1-9]|[1-9][0-9]?))?`, raise naming the builder's purpose and
quoting the stripped value) used by every central-unit builder that
defaults to `where="#0"`: `set_central_mode`, `set_central_temperature`,
`set_central_program`, `set_central_scenario`, `set_central_weekend`,
`set_central_holiday`, `set_holiday_end_date`, `set_holiday_end_time`
and `set_timed_manual_end_time`. Each now refuses a bare zone
(`*4*3101*1##`, `*michnovka#4*1*OpenWebNet-HA#30*...##` used to be built), `#Z`, `#Z#C`,
`#00`, `#0#0`, three digits, `None` and `True`, and accepts `#0` /
`#0#C`.

Tests: `tests/test_who4_central_mode_address.py` follows the contract
(`#Z` / `#Z#C` refused, the parity test became "set_central_mode refuses
what set_mode builds", the reversed "#Z#C: probe C of zone Z" comment is
gone), the new `tests/test_who4_central_unit_where.py` covers every
builder (`#0`, `#0#1`, `#0#12`, `#0#05` accepted; `1`, `michnovka#1`, `OpenWebNet-HA#23#1`,
`#00`, `#0#0`, `#0#100`, `None`, `True` refused; whitespace; defaults),
and the dimension 22 guard fixtures address the 4-zone central unit
`#0#4` instead of `michnovka#4`. Statement coverage of `OWNd/message/heating.py`
stays at 100 %.

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

gdluck commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Both architectural points are in 2583a43, thanks for the libqtdevices / thermo-ts10 reading:

  1. set_central_mode takes the central unit only: _WHO4_CENTRAL_WHERE_REGEX = #0(?:#(?:0[1-9]|[1-9][0-9]?))? (#0, #0#C with C 1..99 / 01..99). #Z and #Z#C are refused with a ValueError that points at set_mode, and the test test_set_central_mode_refuses_what_set_mode_builds pins that set_mode("23#1") builds *4*311*#23#1## while set_central_mode("#23#1") raises; the reversed "probe C of zone Z" comment is gone.
  2. One _validate_central_where(where, purpose) guards all nine central-unit builders (set_central_mode, set_central_temperature, set_central_program, set_central_scenario, set_central_weekend, set_central_holiday, set_holiday_end_date, set_holiday_end_time, set_timed_manual_end_time): signatures and frames unchanged, each refuses a bare 1, #1, #23#1, #00, #0#0, #0#100 and accepts #0, #0#1, #0#12, #0#05 (new tests/test_who4_central_unit_where.py, 120 tests; the dimension-22 guard fixtures use #0#4 instead of #4).

Checks: 2302 passed, 100 % statements, ruff, mypy, golden corpus, library standards and docs check clean; the MyHOME v2-phase1-architecture suite against this head: 5044 passed.

One caveat worth your eye on the MyHOME side: climate.py calls these two builders only under self._central, but yaml central: true can force that on a non-central WHERE (where: "2"); such a config now raises instead of writing *#4*2*#14*…##. Bus-discovered devices are central only for #0 / #0#1, and no MyHOME test exercises the yaml path; I have not touched MyHOME.

Housekeeping: references updated (#799 merged, #143 closed). The clean rebase onto 12fef0c8 without c82d88f and the authorship reset on e8e20fb / 406d155 need a force push, which I will do as soon as the branch owner authorises it; the content is final.

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck please check ci failing for all prs in ownd

@gdluck

gdluck commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Checked: the Tests & Coverage jobs on #137, #138 and #142 (all four Python versions) die before collecting a test with INTERNALERROR> pytest.PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset. pytest-asyncio 1.4.1, released after master's last green run, now emits that warning in pytest_configure, and pyproject.toml's filterwarnings = error::DeprecationWarning turns it into an abort (exit code 3). It is independent of the PR contents; master 12fef0c reproduces it locally with pytest 9.2.0 / pytest-asyncio 1.4.1 and would fail its next run the same way. #139's only red job is the MyHOME suite, red by design (pending_decoder_rx).

Fix: #150 adds asyncio_default_fixture_loop_scope = "function" to [tool.pytest.ini_options] (the scope every async fixture already uses; 2125 passed locally with the same plugin version). Once it lands, the open PRs go green on rebase.

…shared guard

`set_central_mode("1", mode)` built `*4*311*1##`, which is nack/silent on
all 12 emulated gateways and on a live F461 2.0.11: central-unit WHATs
address the central unit by API contract (OWNd#77 fix 3). The builder now
requires a central-unit address and the contract is shared by every
central-unit builder.

- `_WHO4_CENTRAL_WHERE_REGEX = #0(?:#(?:0[1-9]|[1-9][0-9]?))?` (ASCII):
  `#0` (3550, 99 zones) or `#0#C` with C = 1..99 / 01..99 (4695, the
  unit's configured zone, MyHOME_Suite OPEN.db `#0#[ZA][ZB]` and
  MHCatalogue 4695 `ZAZB_CENTRAL`; oracle thermo-central-mode
  `*4*0*#0#2##` -> `D1 02`). `#Z` and `#Z#C` are zone / probe addresses
  (ControlledProbeDevice, bus command byte C2) handled by `set_mode`,
  which already builds `*4*311*OpenWebNet-HA#23##` and `*4*311*OpenWebNet-HA#23#1##`; they are
  refused with a ValueError that names `set_mode`.
- One `_validate_central_where(where, purpose)` (strip, fullmatch, quoted
  error) is the first statement of all nine central-unit builders:
  set_central_mode, set_central_temperature, set_central_program,
  set_central_scenario, set_central_weekend, set_central_holiday,
  set_holiday_end_date, set_holiday_end_time, set_timed_manual_end_time.
  Signatures and frames are unchanged; each refuses a bare `1`, `michnovka#1`,
  `OpenWebNet-HA#23#1`, `#00`, `#0#0`, `#0#100`, None and True, and accepts `#0`,
  `#0#1`, `#0#12`, `#0#05` (whitespace stripped).
- Tests: tests/test_who4_central_mode_address.py (49 cases, incl.
  "set_central_mode refuses what set_mode builds"),
  tests/test_who4_central_unit_where.py (every builder, 120 cases); the
  dimension-22 guard fixtures use `#0#4` instead of the no-longer-valid
  `michnovka#4`.

Reviews 5642254816-era (OWNd#77), 5684988147 (OpenWebNet-HA#137) and the OpenWebNet-HA#148 review
on the selector bound are folded in; squashed on master 4213cab as asked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/who4-set-central-mode-address branch from 2583a43 to 938b98b Compare October 28, 2026 16:01
@gdluck

gdluck commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Housekeeping done: the branch is now a single commit 938b98b on master 4213cab (no merge commit, authorship reset, the earlier revisions folded in; tree identical to the reviewed 2583a43 for heating.py and the tests, plus the CHANGELOG bullet under Fixed). 2322 passed, ruff / mypy / docs check clean. #150 is in, so CI should run through now.

@GreenGrassBlueOcean GreenGrassBlueOcean left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gdluck — all points from review #5684988147 are cleanly addressed in 938b98b:

  1. Central Unit WHERE contract (#0 / #0#C): _WHO4_CENTRAL_WHERE_REGEX (#0(?:#(?:0[1-9]|[1-9][0-9]?))?, re.ASCII) accurately restricts Central Unit builders (ThermalDevice, bus command byte C1) to 3550 (#0) and 4695 (#0#1..99 / #0#01..99), while rejecting #Z / #Z#C zone/probe addresses (ControlledProbeDevice, bus command byte C2) with a clear ValueError pointing to set_mode(...).
  2. Consistent guard across all 9 builders: Sharing _validate_central_where(where, purpose) across set_central_mode, set_central_temperature, set_central_program, set_central_scenario, set_central_weekend, set_central_holiday, set_holiday_end_date, set_holiday_end_time, and set_timed_manual_end_time closes the bare-zone gap uniformly, backed by tests/test_who4_central_mode_address.py, tests/test_who4_central_unit_where.py, and the updated #0#4 cases in tests/test_who4_dimension22_guard.py.
  3. MyHOME YAML central: true note: Checked MyHOME (validate.py / climate.py) — climate_schema takes zone: (not where:), and MyHomeDeviceSchema already normalizes zone: <C> with central: true to #0#<C>, so valid YAML central configs continue to pass _validate_central_where.
  4. History & CI: Clean single commit on master (4213cab), CHANGELOG.md updated, and all 8 CI gates (including MyHOME test suite and Golden corpus & smoke gates) are green.

(Minor non-blocking note: the ## State Diagram and ## Testing sections in the GitHub PR description still mention #Z | #Z#C from the earlier revision, though the commit message and docstrings in 938b98b are already updated.)

LGTM — ready to merge once marked Ready for review.

@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as ready for review October 28, 2026 18:52
@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 4e1ddc2 into OpenWebNet-HA:master Oct 28, 2026
8 checks passed
GreenGrassBlueOcean pushed a commit that referenced this pull request Nov 2, 2026
OWNd/__init__.py: __version__ = "2.0.0b12". CHANGELOG.md: [Unreleased]
promoted to [2.0.0b12] - 2026-11-01 (every PR merged since v2.0.0-b11:
#134, #135, #136, #137, #138, #139, #140, #142, #145, #147, #148, #150,
#151) with a fresh empty [Unreleased] header.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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