Repository navigation
fix(gateway): skip the startup requests the firmware refuses, poll each zone's temperature on SCS-only gateways - #794
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… poll of OpenWebNet-HA#794 The merged OpenWebNet-HA#793 and OpenWebNet-HA#794 both act on the F454 / MH202 zone update: OpenWebNet-HA#794 adds the *michnovka#4*Z*0## temperature request on SCS-only gateways, OpenWebNet-HA#793 keeps *michnovka#4*Z*60## out. The test now asserts the humidity frame is absent instead of an empty send list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
911c77d to
d4284e4
Compare
d4284e4 to
5dbb70a
Compare
|
@gdluck please rebase and solve conflcts |
5dbb70a to
f1b1d9e
Compare
|
Rebased on the current base (head f1b1d9e, mergeable): |
f1b1d9e to
9b9b12a
Compare
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Solid protocol direction — verified GENERAL_ZONE_REQUEST_REFUSED and SOUND_STATUS_REQUEST_REFUSED 1:1 against own-firmware-oracle (trace-corpus.tsv, sound-who16-source.tsv, thermo-ts10.tsv), the live F461 (2.0.11) and MyHomeServer1 (3.87.13) captures, and OWNd==2.0.0b11.
Requesting changes for a few concrete issues before undrafting/merging (inline comments attached, plus two cross-file items outside the diff hunks below):
F450/F460mismatch (gateway.py,climate.md,test_startup_requests_per_firmware.py): Neither"F450"nor"F460"is insupports_zone_status's exclusion list, sozone_temperature_poll_neededreturnsFalsefor both — causing legacy SCS gatewayF450(020010) to send*#4*<WHERE>##(#674 timeout hazard) instead of*#4*<WHERE>*0##, whileclimate.mdmisclassifiesF461-siblingF460(020012) as SCS-only.- F422 interface-routed zones (
climate.py):*#4*2#4#01*0##isnack, silent(dropped locally by firmware) onF450,F454,F459, andMH202inOpenWebNet-HA/own-firmware-oracle#60. myhome.sweep_bus(custom_components/myhome/services.py:241-270): Whenforce=False,sweep_busbuilds its owngeneral_querieslist and gates only on_handler_supports_who(handler, who)without checkinghandler._refused_by_gateway(q). As a result,myhome.sweep_bus(force=False) still sends refused*#4*0##and*#16*0*5##frames and waitsBUS_QUIET_PERIODper NACKed frame insend_paced(). Please filtergeneralbynot handler._refused_by_gateway(q)whennot force(or reusehandler._discovery_frames()) and add a test forforce=Falsevsforce=True.- Published oracle row for
*#4*Z*0##(Draft blocker in PR description): The PR body states there is no published*#4*Z*0##row yet, blocking onown-firmware-oracle#60. Note that*#4*1*0##is already published across all 12 emulated gateways inresults/<P>/<V>/oracle/full/thermo-ts10.tsv(and already intests/golden/firmware_oracle.json, checked byscripts/update_gateway_profiles.py:502):ack, out(93 01 14 00) onMH202andnack, out(93 01 14 00/$004990001000\r/ internal bus payload) on the other 11 models. You can citethermo-ts10.tsvdirectly alongside#60(which adds zone 2,#0/#23#1, and#4#01coverage), so this PR doesn't need to wait on#60once the items here are addressed.
9b9b12a to
5985b69
Compare
|
Thanks for the thorough pass; every item is on the branch (head 5985b69, rebased on the current base, one commit per point):
Full suite 4584 passed / 1 skipped at 100 % coverage; ruff, typing ratchet, HA standards, docs sync and translations clean; |
|
All checks green on 5985b69; marking ready for review. |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thanks for working through the items from review 5642254816 (F450 / F460 split, HL4684 + OWNd aliases, F422 Z#4#II guard, sweep_bus(force=...), current_temperature_restored).
Cross-checking 5985b696 against both the existing physical traces in tests/fixtures/ and the newly ingested live captures (#799) leaves six concrete items to resolve before merge (inline below):
MH201double-queries*#4*0##+*#4*Z*0##and overlooks two committed physicalMH201traces (gateway.py:158-171,runtime_behaviour.md:15/37,2.0.0b16.md).- Keep
*#4*Z*0##as the SCS-only startup baseline here, but fix the inaccurate protocol comments & PR body claims (climate.py:576-580,climate.py:597-604) that conflate#674'sF45510 s socket timeout and the busless QEMU oracle'sNACKwith liveF454 + #0(issue_629) and populated*#4*Z*0##(f461_live_2026-10-23). Cold-start#0subordinate-zone setpoint hydration is tracked separately in #813 so this PR stays focused. - Make
_polls_temperaturea pure property (climate.py:397, 412-433) — right now it mutates_interface_poll_skip_loggedand logsDEBUGduring__init__before the entity is attached tohassor the registry. - Resolve the self-contradiction in
docs/getting-started/hardware-compatibility.md(L55-66) /scripts/update_gateway_profiles.py(L183-236), where Column 2 (Initial Discovery Subsystems Queried) still lists4and16on models whose Column 3 note says the sweep omits them, and add the missingMH201/HL4684notes. - Document in
docs/configuration/services.md(L139-145) thatmyhome.sweep_bus(force: false) now sends zero WHO 4 frames on the gateways refusing*#4*0##(and does not trigger per-zone polls). - Back
tests/test_startup_requests_per_firmware.pywith real fixture frames (it currently loads zero trace files and uses synthetic Zone3frames despite the PR body stating trace frames are inlined there), and tighten the PR body'srun6_event_session_ha_fixed_integrationtable entry (run6is anevt-onlyrxcapture and cannot by itself provetxomissions).
(Separate trace-corpus schema, timestamp causality, issue_674 forensics, and fixture privacy-gate follow-ups are tracked in #814.)
5985b69 to
d3be161
Compare
|
Thanks for the second pass; all six are on the branch (head d3be161, rebased on 089fef5, one commit per item).
Full suite 4631 passed / 1 skipped, |
…del before the table lookup `manifest.json` matches the H4684 over SSDP as `modelName: HL4684`, a spelling OWNd has no alias for (it knows l4684, n4684, nt4684, lgrh4684), and an SSDP identity is never overwritten by WHO 13 / 1013. "H4684" is not a substring of "HL4684" or of "BTICINOL4684", so an SSDP-discovered or brand-prefixed H4684 missed the humidity table and was polled (review 5644219403, finding 3). `_model_key` now resolves a brand-prefixed or catalogue string word by word through OWNd's own aliases (`"BTicino L4684"` -> H4684, `"Legrand 003598"` -> F454, `"Legrand 003565"` -> MH200N, kept distinct from MH200), maps the SSDP spelling `HL4684` (and `HC4684`) to `H4684` the way OpenWebNet-HA#794 does, and only then falls back to the longest-first substring lookup. OpenWebNet-HA#794's two `_model_key` tests are copied as they are (`test_aliased_model_strings_follow_their_profile`, `test_ssdp_model_string_hl4684_is_keyed_as_h4684`); eleven brand-prefixed and catalogue strings and two humidity zones (`HL4684`, `BTicino L4684`) are added. 12 fail before, 85 pass after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…del before the table lookup `manifest.json` matches the H4684 over SSDP as `modelName: HL4684`, a spelling OWNd has no alias for (it knows l4684, n4684, nt4684, lgrh4684), and an SSDP identity is never overwritten by WHO 13 / 1013. "H4684" is not a substring of "HL4684" or of "BTICINOL4684", so an SSDP-discovered or brand-prefixed H4684 missed the humidity table and was polled (review 5644219403, finding 3). `_model_key` now resolves a brand-prefixed or catalogue string word by word through OWNd's own aliases (`"BTicino L4684"` -> H4684, `"Legrand 003598"` -> F454, `"Legrand 003565"` -> MH200N, kept distinct from MH200), maps the SSDP spelling `HL4684` (and `HC4684`) to `H4684` the way OpenWebNet-HA#794 does, and only then falls back to the longest-first substring lookup. OpenWebNet-HA#794's two `_model_key` tests are copied as they are (`test_aliased_model_strings_follow_their_profile`, `test_ssdp_model_string_hl4684_is_keyed_as_h4684`); eleven brand-prefixed and catalogue strings and two humidity zones (`HL4684`, `BTicino L4684`) are added. 12 fail before, 85 pass after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@gdluck please rebase |
d3be161 to
b1c4716
Compare
|
Rebased onto |
|
@gdluck please rebase |
…ch zone's temperature on SCS-only gateways own-firmware-oracle (results/mcp_index.json at 53f9514) shows *michnovka#4*0## NACKed with nothing on the bus by F450, F454, F459, F460, F461, H4684, MH200, MH202 and MyHomeServer1 (trace-corpus suite) and *OpenWebNet-HA#16*0*5## by F450, F459, F460, F461, MH202 and MyHomeServer1 (sound-who16-source suite). The startup sweep sent both everywhere, and on SCS-only gateways a standalone thermostat stayed unknown after a restart until it next reported by itself (OpenWebNet-HA#185 MH201, OpenWebNet-HA#257 F461, OpenWebNet-HA#674). The sweep leaves those two requests out on the models that drop them; the OWNd profile is read first (supports_general_heating_status, supports_who(16)) with the MyHOME table as the fallback. On the SCS-only models among them and on the MH201 each climate zone asks for its own temperature with *michnovka#4*Z*0## after the sweep, which every emulated gateway forwards; a bus-discovered zone keeps that poll unless the revealing frame carried its temperature. The last current_temperature is restored with the attribute current_temperature_restored until the zone reports again. The MH200 sweep expectation drops *michnovka#4*0## accordingly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e status poll (review follow-up) Review 5642254816, point 1. The F450 (020010) was in GENERAL_ZONE_REQUEST_REFUSED but not in the supports_zone_status exclusion tuple, so its zones kept sending *michnovka#4*Z##, which the firmware forwards to the bus like the F454 (trace-corpus.tsv: the same four raw SCS dimension telegrams and a NACK, the OpenWebNet-HA#674 timeout), and zone_temperature_poll_needed stayed False. "F450" joins the tuple, so each of its zones asks *michnovka#4*Z*0## instead. The F460 (020012) expands *michnovka#4*Z## like the F461 ($00499013000 in trace-corpus.tsv), so supports_zone_status stays True and it leaves ZONE_TEMPERATURE_POLL, which is now an explicit table of the SCS-only models that refuse *michnovka#4*0## plus the MH201 (OpenWebNet-HA#185); F460, F461 and MyHomeServer1 are documented as answering the zone status poll themselves. climate.md and runtime_behaviour.md say which models poll *michnovka#4*<WHERE>## and which ask *michnovka#4*<WHERE>*0##. The zone temperature poll table gains F450 (True) and F460 (False). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ops Z#4#II requests (review follow-up) Review 5642254816, point 2. own-firmware-oracle PR OpenWebNet-HA#60 (thermo-zone-requests suite, not published yet) shows *michnovka#4*2#4#01*0## nack/silent on F450, F454, F459, F460, F461, F453, F453AV, MH200N, MH202 and MyHomeServer1 and forwarded only on H4684 and MH200; the MH202 crashes its thermoregulation daemon on the dimensionless *michnovka#4*2#4#01##. Of the SCS-only models that poll *michnovka#4*Z*0##, the F450, F454, F459 and MH202 therefore cannot poll a zone behind an interface. A new handler property interface_zone_request_refused reads the OWNd profile first (supports_interface_zone_requests, which belongs in OWNd's profiles) and falls back to INTERFACE_ZONE_REQUEST_REFUSED, the oracle row. _polls_temperature is False for an interface-routed zone on such a gateway, logged once at DEBUG, so the zone keeps its restored reading until the bus reports it; the H4684, MH200 and MH201 (not in the oracle) poll *michnovka#4*Z#4#II*0##. The deliberate omission of the _poll_answered callback on the dimension 0 request is now commented: *michnovka#4*Z*0## ends with a NACK on the command session on 11 of the 12 oracle gateways even when forwarded (thermo-ts10.tsv), which cancels the written future and would count a healthy zone as unresponsive. Tests: four models whose interface zones send nothing (and log once), three that poll *michnovka#4*3#4#01*0##, the profile flag over the table both ways. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… drops unless forced (review follow-up) Review 5642254816, point 3. myhome.sweep_bus built its own general request list gated on the profile's WHOs only, so with force=False it still sent *michnovka#4*0## and *OpenWebNet-HA#16*0*5## to the gateways whose firmware drops them, each costing a NACK and a BUS_QUIET_PERIOD wait in send_paced. The service now applies the same firmware gate as the startup sweep (handler._refused_by_gateway, read through a helper that tolerates handler doubles); force=True sends everything, as the services.yaml and services.md descriptions now say. Tests: an F461 sweep sends *#1*0## and *#2*0## with force=False and all five general requests with force=True; the multi-gateway service test expects the MH202 secondary to leave out *michnovka#4*0## and *OpenWebNet-HA#16*0*5## (43 status requests instead of 45). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n the H4684 tables (review follow-up) Review 5642254816, point 4. manifest.json registers the H4684 under the SSDP modelName "HL4684", which OWNd master does not alias to its H4684 profile (it knows h4684, l4684, n4684, nt4684, lgrh4684, lgr4684), so such a gateway gets the Generic profile with model_name "HL4684": "H4684" in norm was False in supports_zone_status and _model_key missed GENERAL_ZONE_REQUEST_REFUSED and ZONE_TEMPERATURE_POLL. _model_key now maps HL4684 to H4684 and the supports_zone_status exclusion tuple lists HL4684, until OWNd aliases it. The same substring check missed the spellings OWNd does alias (L4684, N4684, NT4684, LGR4684 and the MyHOME_Suite item numbers such as 003598 for the F454): an L4684 was keyed on the H4684 tables (its profile is the H4684's) but kept supports_zone_status True, so it skipped *michnovka#4*0## and still polled *michnovka#4*Z## instead of *michnovka#4*Z*0##. supports_zone_status now also judges the model OWNd resolves the configured string to (get_gateway_profile(model).model_name). Tests: HL4684 in the sweep and zone temperature poll tables, a pinned test of the key, the zone status and the three refusal properties, and L4684, NT4684, 003598 and 03535 following their profile. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ew follow-up) Review 5642254816, point 5. The Day 0 blank-start matrix in runtime_behaviour.md said WHO 4 and WHO 16 are discovered with *michnovka#4*0## and *OpenWebNet-HA#16*0*5## unconditionally; both rows now name the models whose firmware drops the request and say that a zone or amplifier there is discovered from its first bus frame. GATEWAY_DISCOVERY_METADATA (scripts/update_gateway_profiles.py) no longer claims "Sound (*OpenWebNet-HA#16*0*5##)" for the MH202 (77 of 77 WHO 16 frames silent in sound-who16-source.tsv) and notes the dropped requests on the F454, F461, MyHomeServer1 and MH200 rows; hardware-compatibility.md is regenerated with scripts/sync_documentation.py --update. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…weep leaves it out (review follow-up) A physical MH201 3.3.6 (WHO 13 code 200) sent *michnovka#4*0## during a startup sweep and no zone frame followed, although zones 2, 3 and 4 report on that plant: compare tests/fixtures/traces/issue_466/myhome_sweep_MH201_all_2026-09-25T19-11-35.json frame 11 with myhome_trace_MH201_all_2026-09-25T19-05-42.json frames 0-10. Until now the MH201 was only in ZONE_TEMPERATURE_POLL, so every start sent the refused general request, waited the quiet period and then polled each zone anyway. "MH201" joins GENERAL_ZONE_REQUEST_REFUSED; runtime_behaviour.md, climate.md and the b16 release note list it; the tests that used an MH201 as the default sweep model drop *michnovka#4*0## from their expectations (the delegated-WHO 4 follower test moves to an MH200N, whose firmware forwards the request). Refs OpenWebNet-HA#185. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…planations in the poll comments (review follow-up) The comments around the zone poll said that SCS gateways forward *michnovka#4*Z## to thermostats that have no handler for it, with a 10 s timeout per zone (OpenWebNet-HA#674), and that *michnovka#4*Z*0## ends with a NACK on live zones. Neither matches the evidence: the emulated SCS gateways expand *michnovka#4*Z## into four bus queries ($0499ZZ3000 / 12 / 0E / 0D in trace-corpus.tsv) and an F454 with a 99-zone central unit answers *michnovka#4*1##..*michnovka#4*15## in ~1.3 s each (issue_629 log, lines 489-700); the 10 s hang of OpenWebNet-HA#674 was an F455 on actuator-only channels. The *michnovka#4*1*0## NACK on 11 of 12 oracle images is the bus TX timeout of an image with no thermostat wired, the same NACK the live F461 gives for its unpopulated zone 1 after 2.07 s (f461_live_2026-10-23 rows 374-376), while its populated zone 2 answers *michnovka#4*2*0## with *michnovka#4*2*0*0239## and an ACK in 119 ms (rows 521-525). The per-zone dimension 0 request stays (one bus query, ~120 ms, kind to single-worker gateways); the _poll_answered omission now says it waits for a command-session capture of *michnovka#4*Z*0## on an SCS-only gateway. Cold-start hydration of the subordinate zones through the central unit is OpenWebNet-HA#813. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…withheld F422 poll in async_update (review follow-up) The property mutated _interface_poll_skip_logged and wrote the one-shot DEBUG line while __init__ computed _poll_on_add, before the entity had an entity_id or was attached to hass, and never when async_update actually skipped the poll. It is now a pure boolean built from two pure helpers (_temperature_poll_wanted, _interface_poll_refused); async_update logs once, at DEBUG, when it withholds the poll of a zone behind an F422. Tests: nothing is logged by construction or by reading the property, one line on the first update and none on the second; an F455 zone behind an interface logs nothing (no poll is wanted there). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s and the requests the sweep sends (review follow-up) "Initial Discovery Subsystems Queried" listed WHO 4 and 16 for models whose note said the sweep leaves them out. The column could not simply drop them: calibrate_discovery_with_ownd recomputes queried_whos from the OWNd profile and verify_profiles_against_oracle requires the two to match, so the column is split instead. Column 2 stays the profile's WHOs (still validated against the oracle), a new column 3 is what the sweep sends, derived from a per-row sweep_omits list naming the general requests the firmware drops (*michnovka#4*0## / *OpenWebNet-HA#16*0*5##; the validator rejects any other frame or a WHO the profile does not advertise), and a test compares sweep_omits with MyHOMEGatewayHandler._discovery_frames() for every row. MH200 and MH200N get separate rows (the MH200 drops *michnovka#4*0##, the MH200N forwards it), the MH201 row names its *michnovka#4*0## refusal (issue_466 physical sweep trace) and its per-zone *michnovka#4*<WHERE>*0## poll, and the H4684 / HL4684 gets a row. hardware-compatibility.md is regenerated with sync_documentation.py --update. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the gateways that drop *michnovka#4*0## (review follow-up) myhome.sweep_bus with force: false applies the startup sweep's firmware gate, so on F450, F454, F459, F460, F461, H4684, MH200, MH201, MH202 and MyHomeServer1 it sends zero WHO 4 frames (no *michnovka#4*0##, and the service triggers no per-zone *michnovka#4*<WHERE>## or *michnovka#4*<WHERE>*0## poll); *OpenWebNet-HA#16*0*5## is left out the same way on the six models that drop it. force: true sends them and the firmware drops them before the bus. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…MH201 traces in the per-firmware tests (review follow-up) The module synthesised zone 3 frames and loaded no trace. It now reads three committed captures with the loaders the other trace tests use and pins what they show: f461_live_2026-10-23 run 1 (*michnovka#4*2*0## answered with *michnovka#4*2*0*0239## and an ACK in 0.119 s, *michnovka#4*1*0## of the unpopulated zone 1 NACKed after 2.071 s, *michnovka#4*0## refused in under 10 ms), myhomeserver1_live_2026-10-24 (33 zones 35..70 reporting *michnovka#4*Z*0*TTTT##, *michnovka#4*0## refused in 51 ms, *michnovka#4*35## answered with five frames) and the issue_466 MH201 sweep (frame 11 *michnovka#4*0## with no zone frame afterwards, against the sibling trace's zones 2, 3 and 4). The captured replies are replayed through zone entities: the request an SCS-only zone 2 sends is the captured *michnovka#4*2*0##, the captured reply clears the restored temperature, the MyHomeServer1 zones need no temperature poll and keep *michnovka#4*Z##, the MH201 leaves *michnovka#4*0## out and polls the captured zone 4. A dedicated case shows a cancelled dimension 0 write never reaches _poll_answered. The synthetic zone 3 cases stay (they pin the request, not a plant). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… temperature poll After the rebase on the merged OpenWebNet-HA#793, a humidity-refusing SCS-only zone (F450, F454, MH202) no longer sends nothing: it asks *michnovka#4*Z*0## for its temperature (this PR) and still no *60##. The "never reaches _poll_health" case moves to the models that forward the sweep's *michnovka#4*0## (F453, F453AV, MH200N), the F450 case asserts the temperature poll instead of a zone status it no longer has, and the F422 humidity-zone case uses an F459 (forwards *60##, drops Z#4#II). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
b1c4716 to
13240f2
Compare
|
Rebased again onto |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
All items from reviews 5642254816 and 5642844379 plus the #792 / #793 rebase reconciliations (13240f23) are verified against the firmware oracle and physical traces (f461_live_2026-10-23, myhomeserver1_live_2026-10-24, issue_466), and all 17 CI checks pass. LGTM!
b48e774
into
OpenWebNet-HA:v2-phase1-architecture
… bump (rebase on OpenWebNet-HA#794) After the rebase the discovery rows carry both OpenWebNet-HA#794's `sweep_omits` (the general requests the firmware drops) and this branch's `pinned_whos` (the no-OWNd fallback), while the advertised column is derived from the installed OWNd profile. Two tests pin the interplay at the OWNd OpenWebNet-HA#142 pin bump: - `verify_profiles_against_oracle()` checks `sweep_omits` against `pinned_whos`, so once the F461 / MyHomeServer1 rows drop WHO 16 a leftover `*OpenWebNet-HA#16*0*5##` is flagged (and a `pinned_whos` entry the startup sweep never asks for is flagged too); the F455 / unknown-frame cases of OpenWebNet-HA#794 are unchanged. - Without an importable OWNd, `pinned_whos` without WHO 16 yields the `WHO 1, 2, 4, 18` row, the reduced `(firmware drops `*michnovka#4*0##`)` sent column and the WHO 22 wording, so the CI jobs that run the docs check without OWNd regenerate the same table as the pinned OWNd. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Following up on the MH202 010024 crash alert in own-firmware-oracle#63, we decoded the raw scs_bus_hex telegrams and disassembled libthermo.so / libplug_thermo_tr.so across all twelve emulated gateways (own-firmware-oracle#65, documented in OpenWebNet-Encyclopedia#52 and openwebnet-mcp#22):
-
Zero of the twelve emulated gateways actually route
WHO 4requests across anF422interface ($06EC.../$06E4...):Frame Gateways with bus_outcome=outDecoded scs_bus_hexActual SCS bus effect *#4*2#4#01##,*#4*2#4#12##H4684 020054,MH200 020102$0499023000\r+$0499023012\r+$049902300E\r+$049902300D\rTruncated at #(atoi) -> polls local main-bus zone2*#4*2#4#01##MH202 010024(none — nack, crash)libthermo.soparses trailing#4#01as zone4, actuator1;thermo_open2scs+0x394dereferencesNULL->cosoSIGSEGV(*#4*2#1##crashes identically;2#4#12##is rejected cleanly because12 > 9)*#4*2#4#01*0##H4684 020054,MH200 020102$0499023000\rTruncated at #-> polls local main-bus zone2*#4*2#4#01*19##H4684 020054,MH200 020102$0499023013\rTruncated at #-> polls local main-bus zone2*#4*2#4#01*20##,*#4*2#4#12*20##,*#4*2#1#4#01*20##F450,F454,F459,F460,F461,MyHomeServer1$04A9023010\r/$004A9023010\rTruncated at #-> polls local main-bus zone2*#4*2#4#01*20##,*#4*2#1#4#01*20##MH202 010024$0499843110\r(2#4#12*20##->silent)Misparsed as local zone 4, actuator1*#4*2#4#01*60##F459,F460,F461,MyHomeServer1$06D102030308000000\r/$006D102...Truncated at #-> polls local main-bus zone2*#4*2#4#01*11##(none) (none — 12/12nack, silent)Dropped on all 12 gateways -
We opened #816 against
v2-phase1-architectureto withhold unroutedWHO 4#4#IIpolls acrossgateway.py(_send_zone_requests/interface_zone_request_refused) andclimate.py(status,*11##,*60##) and guard dimensionless*#4*Z#N##. Inline notes below highlight the hunks in#794affected by this so the rebase stays straightforward.
| # firmware drops *#4*Z#4#II*0## (and *#4*Z#4#II##) before the bus, nack/silent on ten of the | ||
| # twelve emulated images; only H4684 and MH200 forward them (own-firmware-oracle PR #60, | ||
| # thermo-zone-requests suite, not published yet). Read from the OWNd profile first | ||
| # (``supports_interface_zone_requests``); this table is the fallback until the flag exists there. | ||
| INTERFACE_ZONE_REQUEST_REFUSED = frozenset( | ||
| {"F450", "F453", "F453AV", "F454", "F459", "F460", "F461", "MH200N", "MH202", "MYHOMESERVER1"} |
There was a problem hiding this comment.
H4684 (020054) and MH200 (020102) do not actually route *#4*2#4#01*0## or *#4*2#4#01## across an F422 interface: their WHO=4 parser reads atoi("2#4#01") == 2 and puts $0499023000\r on the main bus (polling local zone 2, identical bytes on *#4*2#4#12## as well; see own-firmware-oracle#65). Leaving H4684, MH200, MH201, F455, and Generic outside this set means:
- on
H4684/MH200, a zone2#4#01silently cross-polls local zone2; - on a
Genericprofile connected to anMH202 010024,supports_zone_statusisTrue, so a zone2#4#01(01..09) sends*#4*2#4#01##and crashescosowithSIGSEGV.
Defaulting interface_zone_request_refused to True unless getattr(profile, "supports_interface_zone_requests", None) is True (as implemented in #816) avoids both cross-polling and the Generic -> MH202 crash while keeping the explicit profile opt-in.
| def interface_zone_request_refused(self) -> bool: | ||
| """Whether this gateway drops the status requests of a zone behind an F422 (``*#4*Z#4#II*0##``).""" | ||
| supported = getattr(getattr(self.gateway, "profile", None), "supports_interface_zone_requests", None) | ||
| if isinstance(supported, bool): | ||
| return not supported | ||
| return self._model_key in INTERFACE_ZONE_REQUEST_REFUSED |
There was a problem hiding this comment.
To match actuator_request_refused and humidity_request_refused (after #792 / #793), interface_zone_request_refused (and zone_temperature_poll_needed) should evaluate on self._who4_target() so WHO 4 secondary delegation and warm-standby failover consult the gateway that actually transmits the WHO=4 status request.
| def _interface_poll_refused(self) -> bool: | ||
| """Whether the per-zone temperature poll is withheld because the zone sits behind an F422. | ||
|
|
||
| The firmware drops *#4*Z#4#II*0## before the bus on these gateways (own-firmware-oracle | ||
| PR #60: nack/silent on F450, F454, F459 and MH202 among the SCS-only models; the MH202 | ||
| even crashes on *#4*Z#4#II##), so the zone waits for the bus to report instead. | ||
| """ | ||
| gateway = getattr(self, "_gateway_handler", None) | ||
| return ( | ||
| self._temperature_poll_wanted | ||
| and self._interface is not None | ||
| and getattr(gateway, "interface_zone_request_refused", False) is True | ||
| ) |
There was a problem hiding this comment.
Gating _interface_poll_refused on self._temperature_poll_wanted only withholds *#4*Z#4#II*0##, leaving three other #4#II paths open in async_update:
- On
F460,F461, andMyHomeServer1(supports_zone_status=True),*#4*Z#4#II##still goes out (nack, silenton all three), tripping_poll_healthinto a falseunresponsiverepair issue after two polls. - On a
Genericprofile talking to anMH202 010024,*#4*Z#4#01..09##goes out and crashescoso(SIGSEGV). - Lines
671-676below still send*#4*Z#4#II*11##(nack, silenton12/12oracle images) and*#4*Z#4#II*60##(truncated to$06D102030308000000\r/$006D102..., cross-polling local main-bus zoneZonF459,F460,F461, andMyHomeServer1).
Evaluating _interface_poll_refused independently of _temperature_poll_wanted and returning early in async_update when not self._central and self._interface_poll_refused (landed in #816) covers *#4*Z#4#II##, *0##, *11##, and *60## in one place.
| * **Target Temperature**: Adjustable setpoint slider in 0.5 °C increments. | ||
| The slider shows the setpoint the thermostat regulates to (dimension 12, knob offset included) whenever the zone has reported it; the commanded setpoint (dimension 14) is only shown until then. On an F461 the two can differ (21.0 commanded, 23.5 in force on a live capture), and the attribute `local_target_temperature` carries the same value. When the zone leaves `off`, the value reported while it was off is taken as the protection setpoint (#383), so the commanded setpoint is shown until the zone reports dimension 12 again; after a `set_temperature` call the next report, dimension 12 or 14, is shown. | ||
| * **Current Temperature**: Real-time ambient reading reported by the probe. | ||
| On SCS-only gateways whose firmware drops the general request `*#4*0##` (F450, F454, F459, H4684, MH200, MH201, MH202), each zone asks for its own temperature with `*#4*<WHERE>*0##` after the startup sweep, so a standalone thermostat is not `unknown` until it next reports (#185). F460, F461 and MyHomeServer1 drop `*#4*0##` as well but answer the zone status request `*#4*<WHERE>##` themselves (the F460 and F461 expand it into the four dimension requests, the MyHomeServer1 replies from its cache), so their zones keep that poll. A zone behind an F422 bus interface (`WHERE = <zone>#4#<interface>`) is not polled on the F450, F454, F459 and MH202: their firmware drops `*#4*<WHERE>*0##` for such a zone before it reaches the bus (own-firmware-oracle PR #60), so the zone shows its restored reading until it reports by itself; the H4684, MH200 and MH201 poll it. After a restart the last reading is shown with the attribute `current_temperature_restored: true` until the zone reports again. |
There was a problem hiding this comment.
Worth updating this sentence (and the matching parenthetical in .github/release-notes/2.0.0b16.md line 24): in own-firmware-oracle#65, H4684 (020054) and MH200 (020102) do not forward #4#II across an F422 ($06EC / $06E4) — they truncate Z#4#II at # and emit $0499ZZ3000\r on the local main bus, so routed Z#4#II zones should wait for spontaneous reports across all models unless a profile explicitly sets supports_interface_zone_requests=True.
… bump (rebase on OpenWebNet-HA#794) After the rebase the discovery rows carry both OpenWebNet-HA#794's `sweep_omits` (the general requests the firmware drops) and this branch's `pinned_whos` (the no-OWNd fallback), while the advertised column is derived from the installed OWNd profile. Two tests pin the interplay at the OWNd OpenWebNet-HA#142 pin bump: - `verify_profiles_against_oracle()` checks `sweep_omits` against `pinned_whos`, so once the F461 / MyHomeServer1 rows drop WHO 16 a leftover `*OpenWebNet-HA#16*0*5##` is flagged (and a `pinned_whos` entry the startup sweep never asks for is flagged too); the F455 / unknown-frame cases of OpenWebNet-HA#794 are unchanged. - Without an importable OWNd, `pinned_whos` without WHO 16 yields the `WHO 1, 2, 4, 18` row, the reduced `(firmware drops `*michnovka#4*0##`)` sent column and the WHO 22 wording, so the CI jobs that run the docs check without OWNd regenerate the same table as the pinned OWNd. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Requested by A
What is the problem
The startup sweep sends
*#4*0##and*#16*0*5##to every gateway whose profile advertises WHO 4 / WHO 16, but on most firmware these two requests are NACKed by the gateway itself and never reach the bus (own-firmware-oracle, published verdicts; confirmed live on the F461 and on a MyHomeServer1, and on a physical MH201 by the committedissue_466sweep trace). Two consequences: the sweep waits for answers that never come, and on SCS-only gateways (no answer to*#4*Z##from a standalone thermostat) a thermostat staysunknownafter a restart until it next reports by itself, which can take a long time (#185 on an MH201, #257 on an F461).This PR leaves the two requests out on the models whose firmware drops them (startup sweep and
myhome.sweep_busalike), lets each climate zone on those SCS-only gateways ask for its temperature with*#4*Z*0##, and restorescurrent_temperaturefrom the last state with acurrent_temperature_restoredflag until the zone reports again.Refs #185, #257, #674. No open issue is closed by this change. Cold-start hydration of the subordinate zones through the central unit (
#0) is tracked separately in #813; the trace-corpus schema, timestamp causality,issue_674forensics and fixture privacy-gate follow-ups in #814.The per-zone
*#4*Z*0##request rests on the publishedthermo-ts10.tsvrow (forwarded on all twelve emulated gateways) and on the live F461 (answered in 119 ms on a populated zone), so this PR does not wait on OpenWebNet-HA/own-firmware-oracle#60; that PR is cited only for the F422 interface gate (*#4*Z#4#II*0##), which fails closed where it is unverified.What is the solution
gateway.py: three refusal properties, the MyHOME fix(gateway): close idle command sessions before the gateway drops them #381 pattern (the per-model knowledge belongs in OWNd's profiles):general_zone_request_refusedreadsgetattr(profile, "supports_general_heating_status", None)first and falls back toGENERAL_ZONE_REQUEST_REFUSED(F450, F454, F459, F460, F461, H4684, MH200, MH201, MH202, MyHomeServer1) when the flag isNoneor absent. The MH201 is not in the oracle: the entry is the physicalissue_466sweep trace (review point 1, below). No OWNd issue exists for the flag yet; a follow-up OWNd profiles PR should carry it.sound_status_request_refusedreadsprofile.supports_who(16)first and falls back toSOUND_STATUS_REQUEST_REFUSED(F450, F459, F460, F461, MH202, MyHomeServer1). Plain OWNdmasterstill advertises WHO 16 on the F461 and MyHomeServer1 although every WHO 16 frame is refused there; fix(profiles): drop WHO 16 from the F461 and MyHomeServer1 profiles OWNd#142 drops it from those two profiles, after whichprofile.supports_who(16)alone is enough.interface_zone_request_refusedreadsgetattr(profile, "supports_interface_zone_requests", None)first and falls back toINTERFACE_ZONE_REQUEST_REFUSED(F450, F453, F453AV, F454, F459, F460, F461, MH200N, MH202, MyHomeServer1: the ten images on which*#4*2#4#01*0##isnack, silentin feat(oracle): add thermo-zone-requests suite (per-zone polls, actuators, humidity, interface form) own-firmware-oracle#60; H4684 and MH200 forward it). The flag belongs in OWNd as well._discovery_framesskips a request the gateway refuses (DEBUG log). Unknown and forwarding gateways (F453, F453AV, MH200N, F455, Generic) are unchanged. The WHO 18 meter probes are not touched.myhome.sweep_busapplies the same gate throughhandler._refused_by_gateway;force: truesends everything, asservices.yamlandservices.mdsay (withforce: falsethe service sends no WHO 4 frame at all on the listed gateways and triggers no per-zone poll).zone_temperature_poll_needed: only on SCS-only gateways (supports_zone_statusFalse). The profile flag wins when it is stated:supports_general_heating_statusTruemeans the sweep answers and no per-zone poll is needed,Falsemeans it is; only an unverified flag falls back toZONE_TEMPERATURE_POLL, an explicit table (F450, F454, F459, H4684, MH200, MH201, MH202).F450is in thesupports_zone_statusexclusion tuple (its*#4*1##row is the F454's: the request is expanded into four raw SCS dimension queries and the command session ends with a NACK), andF460is not in the table: it expands*#4*Z##into one request like the F461 ($00499013000intrace-corpus.tsv) and answers it, so it keeps the zone status poll.climate.py: on those gateways a zone polls on add withOWNHeatingCommand.get_temperature(*#4*Z*0##) instead of returning early: one bus query, answered in ~120 ms on a populated zone, instead of the four that*#4*Z##expands into (~1.3 s per zone when a central unit answers them all, a NACK after 1.5-2 s when a standalone thermostat answers only some). F455 and other unknown SCS-only gateways still send nothing (the F455 multiple timeouts #674 test is kept). A zone behind an F422 (WHERE Z#4#II) is not polled whereinterface_zone_request_refusedis True (F450, F454, F459, MH202 among the polling models); the H4684, MH200 and MH201 poll*#4*Z#4#II*0##._polls_temperatureis a pure boolean property (built from_temperature_poll_wantedand_interface_poll_refused);async_updatewrites the one-shot DEBUG line when it withholds the poll, also when a fan or humidity request keeps the update going. No_poll_answeredcallback is attached to the dimension 0 request on purpose: the*#4*1*0##NACK on 11 of 12 oracle images is the bus TX timeout of an unpopulated zone (the QEMU images have no thermostat wired; the live F461 NACKs its unpopulated zone 1 the same way after 2.07 s), not a verdict on a populated one, and the result stays uncounted until a command-session capture of*#4*Z*0##on an SCS-only gateway exists.current_temperatureis restored from the last state and flaggedcurrent_temperature_restored: trueuntil the first*#4*Z*0*T##; a malformed restored value is ignored.discovery.py: a bus-discovered zone on such a gateway keeps its poll on add unless the revealing frame carried its temperature._model_keymaps the SSDP model stringHL4684(manifest.json; not an OWNd alias yet) toH4684, andsupports_zone_statusjudges the configured string and the model OWNd resolves it to (get_gateway_profile(model).model_name), soHL4684,L4684,NT4684,003598(F454) and03535(MH202) are treated like the model they stand for instead of polling*#4*Z##.*#4*0##from their expectations (test_blank_start.py,test_multi_gateway.py,test_multi_gateway_regressions.py; the delegated-WHO 4 follower case moves to an MH200N, whose firmware forwards the request);tests/test_issue_304_310_batch.py: the MH200 sweep expectation drops*#4*0##;tests/test_multi_gateway.py: the MH202 secondary'ssweep_busleaves out*#4*0##and*#16*0*5##(43 status requests instead of 45).docs/configuration/runtime_behaviour.md(startup table and Day 0 matrix),gateways.md,climate.md,services.md, andhardware-compatibility.md, whose "Gateway Startup Discovery Capabilities" table now has two WHO columns: the profile's advertised WHOs (validated against the OWNd profile and the oracle, as before) and the requests the sweep actually sends (derived from a per-rowsweep_omitslist inscripts/update_gateway_profiles.py, which a test compares with_discovery_frames()for every row). MH200 and MH200N are separate rows, the MH201 row names its*#4*0##refusal and per-zone*#4*<WHERE>*0##poll, the H4684 / HL4684 has a row. Plus the b16 release-notes delta.Companion PR: OpenWebNet-HA/OWNd#142 (draft): drops WHO 16 from the F461 and MyHomeServer1 profiles (the WHO 16 half of this PR becomes
profile.supports_who(16)alone once it is released). The MyHOME companion of OpenWebNet-HA/OWNd#142 is #808 (draft): the discovery rows are derived fromprofile.supports_who, so it is green against the pinned OWNd and against OpenWebNet-HA/OWNd#142.State Diagram
stateDiagram-v2 direction TB state "1. Startup sweep and myhome.sweep_bus" as Sweep { [*] --> Request : *#4*0## or *#16*0*5## Request --> Sent : profile advertises the WHO and the firmware forwards it, before - always Request --> Sent : sweep_bus with force true Request --> Skipped : *#4*0## with supports_general_heating_status False, or None and model in GENERAL_ZONE_REQUEST_REFUSED, DEBUG Request --> Skipped : *#16*0*5## with no WHO 16 in the profile, or model in SOUND_STATUS_REQUEST_REFUSED, DEBUG } state "2. SCS-only zone without *#4*Z##, before - unknown until it reports" as Zone { [*] --> Restored : last state had current_temperature, current_temperature_restored true [*] --> Unknown : nothing restored Restored --> Polling : zone_temperature_poll_needed, flag False, or None and model in ZONE_TEMPERATURE_POLL Unknown --> Polling : zone_temperature_poll_needed Restored --> Waiting : zone behind an F422 and interface_zone_request_refused, DEBUG once on update Unknown --> Waiting : same gate Polling --> Reported : *#4*Z*0## answered *#4*Z*0*T##, flag cleared Waiting --> Reported : the zone reports by itself Restored --> Reported : the zone reports by itself }Hardware Verification
*#4*0##,*#16*0*5##(and the other 17 WHO 16 forms),*#5*0##are refused by the gateway itself in under 10 ms;*#4*2*0##on the populated zone 2 is answered with*#4*2*0*0239##and an ACK in 119 ms, while*#4*1*0##on the unpopulated zone 1 ends with a NACK after 2.07 s (the bus TX timeout the oracle images show on every zone). With this PR the sweep sends*#1*0##,*#2*0##and the WHO 18 probes, no*#4*0##and no*#16*0*5##; right after a restartcurrent_temperatureshows the last value withcurrent_temperature_restored: true, cleared at the first*#4*2*0*…##.*#4*0##NACK in 51 ms (nothing on the event session), the MyHomeServer1 row of the general-zone table confirmed on 3.87.13;*#16*0*5##and*#16*101*5##NACK in 3 ms; per-zone*#4*35##is answered (5 frames), so the MyHomeServer1 keeps its zone poll and needs no temperature poll; its zones 35..70 report*#4*Z*0*TTTT##by themselves.issue_466captures: the sweep trace sent*#4*0##(frame 11) and no zone frame followed (the only WHO 4 traffic afterwards is three*#4*0#0*17##broadcasts and zone 105's dimension 15), while the sibling trace of the same plant shows zones 2, 3 and 4 reporting (*#4*4*0*0241##,*#4*2*14*0100*3##,*#4*3*12*0090*3##). That is the MH201 entry ofGENERAL_ZONE_REQUEST_REFUSED. The MH201 3.6.27 fresh-discovery log in the repository carries no WHO 4 traffic at all (the plant has no climate zone configured), so it neither confirms nor contradicts it.Live retest (2026-10-24)
Consensus label: confirmed for this startup configuration (one F461, standalone zone 2; other models not tested). Four starts of a local Home Assistant with the combined branches. The HA debug log is the evidence for the requests that were not sent: at each start it logged
Skipping `*#4*0##`: F461 firmware refuses it.,Skipping WHO=5 discovery: not supported by F461 profile.andSkipping WHO=16 discovery: not supported by F461 profile.(the WHO 16 line comes from OpenWebNet-HA/OWNd#142's profile, installed in that HA), and no*#4*0##,*#16*0*5##or*#5*0##appears among its sent frames; the bus monitor history of the same starts shows the sent requests*#1*0##,*#2*0##,*#18*51*51##,*#18*51*113##,*#2*05##,*#4*2##,*#4*2*11##,*#4*2*60##,*#4*#0*20##. Zone 2 showedcurrent_temperature: 24.0from*#4*2*0*0240##and nocurrent_temperature_restoredflag (the zone had reported). The*#4*2*11##NACK at every poll is pre-existing upstream code (climate.py, fan-coil dimension), not touched here. The review follow-up commits change nothing on the F461 path (its sweep, zone poll andsweep_busgating are covered bytest_f461_sweep_in_fullandtest_sweep_bus_leaves_out_the_refused_requests_unless_forced); they were not retested live.How is it tested
tests/test_startup_requests_per_firmware.py(70 cases): the sweep per model for*#4*0##and*#16*0*5##(15 models, MH201 and HL4684 included), the F461 sweep in full, thehardware-compatibility.mdsweep column against_discovery_frames()for every row,myhome.sweep_buson an F461 withforceFalse (*#1*0##,*#2*0##) and True (all five general requests), the two profile-flag overrides, thezone_temperature_poll_neededtable (15 models),test_profile_flag_wins_over_the_zone_temperature_poll_table(×4), the HL4684 key and the aliased spellings L4684 / NT4684 / 003598 / 03535, MH201 and MH202 zones send*#4*3*0##, an F422-routed zone on F450 / F454 / F459 / MH202 sends nothing and is logged once at DEBUG byasync_update(nothing by__init__or the property; also when a humidity request keeps the update going) while H4684 / MH200 / MH201 send*#4*3#4#01*0##, the interface profile flag over the table both ways, an F455 zone sends nothing, a central unit never polls temperature, a bus-discovered zone keeps its poll until a temperature arrives, the restored temperature and its flag, a malformed restored value, a cancelled dimension 0 write never reaches_poll_answered.f461_live_2026-10-23run 1:*#4*2*0##answered with*#4*2*0*0239##and an ACK in 0.119 s,*#4*1*0##NACKed after 2.071 s,*#4*0##refused locally; an MH202 zone 2 sends exactly the captured*#4*2*0##and the captured reply clears its restored temperature.myhomeserver1_live_2026-10-24:*#4*0##refused in 0.051 s,*#4*35##answered with five frames, the 33 zones 35..70 of run 1 replayed through zones on a MyHomeServer1 handler (no temperature poll,*#4*35##kept).issue_466MH201 sweep: frame 11*#4*0##with no zone frame afterwards, the sibling trace's zones 2, 3 and 4; an MH201 handler leaves*#4*0##out and its zone 4 polls*#4*4*0##, the captured*#4*4*0*0241##gives 24.1. The synthetic zone 3 cases stay: they pin the request a zone sends, not a plant.tests/test_gateway_profiles.py: the regenerated discovery table rows (both WHO columns) and thesweep_omitsvalidator (a frame of a WHO the profile does not advertise, or a frame other than*#4*0##/*#16*0*5##, is a violation).tests/test_services.py::test_handler_refuses_frame_only_on_an_explicit_true: thesweep_bushelper never hides a request on a handler without the gate or on a double returning a non-bool.test_gateway_profiles.pycases, and the four MH201 sweep expectations intest_multi_gateway.py,test_blank_start.pyandtest_multi_gateway_regressions.py); all pass with the change.test_issue_304_310_batch.py::test_discovery_asks_an_mh200_for_its_amplifiersis updated for the intended change;test_issue_674_f455_climate_trace.py(includingtest_gateway_supports_zone_status_property) passes unchanged.master, f04d8aa, onPYTHONPATH): 4631 passed, 1 skipped, 100.0 % statement coverage ofcustom_components(16233 statements).ruff check .,scripts/typing_ratchet.py(0 strict errors),scripts/verify_ha_standards.py,scripts/sync_documentation.py --check,scripts/manage_translations.py check: all pass.Where are the traces
Trace PR: #799 (merged,
ad8431ff); the MH201 captures are theissue_466fixtures already in the repository.tests/fixtures/traces/f461_live_2026-10-23/myhome_trace_F461_live_2026-10-23.json, runrun1_oracle_replay_readonly*#4*0##,*#16*0*5##(all 18 WHO 16 forms),*#5*0##NACK in under 10 ms, nothing on the event sessionrun1_oracle_replay_readonly(plant_reads, rows 521-525)*#4*2*0##→*#4*2*0*0239##+ ACK in 0.119 s;*#4*1*0##(unpopulated zone 1) NACK after 2.071 s (rows 374-376)run6_event_session_ha_fixed_integration(an event-sessionrxcapture: it shows replies, not what HA did not send)*#1*0##,*#2*0##,*#4*2##,*#4*2*60##,*#4*#0*20##answered on the event session; the omission of*#4*0##/*#16*0*5##/*#5*0##is verified by the HA debug log of the live retest above, not by this runtests/fixtures/traces/myhomeserver1_live_2026-10-24/myhome_trace_MyHomeServer1_live_2026-10-24.json, runsrun1_identify,run2_reads_thermo_sound_alarm_lightsandrun3_reads_lights_addresses_humidity*#4*0##NACK 51 ms (run 3);*#16*0*5##,*#16*101*5##NACK 3 ms (run 2);*#4*35##answered with 5 frames (run 2); zones 35..70*#4*Z*0*TTTT##(run 1)*#4*0##on a physical MH201tests/fixtures/traces/issue_466/myhome_sweep_MH201_all_2026-09-25T19-11-35.json(frame 11) againstmyhome_trace_MH201_all_2026-09-25T19-05-42.json(frames 0-10)*#4*0##sent, no zone frame follows; zones 2, 3 and 4 report on the same planttests/test_startup_requests_per_firmware.pyloads the first, fourth and fifth archives and replays their rows (see "How is it tested").What does the Firmware Oracle / Encyclopedia say
functional/who-4-temperature-control/addressing.md,dimensions.md):WHERE 0addresses the general probes ("all probes");DIMENSION 0"measured temperature" is read-only, four decimal digits0000..0500. The status request*#4*0##is the general form of the zone status request; the encyclopedia does not say which gateway firmware forwards it.results/mcp_index.json53f9514):*#4*0##(results/<P>/<V>/oracle/full/trace-corpus.tsv): nack, silent on F450020010, F454020051, F459020105, F460020012, F461020011, H4684020054, MH200020102, MH202010024, MyHomeServer1028206; nack, out (forwarded) on F453010100, F453AV030014, MH200N010108. That row plus the MH201 capture isGENERAL_ZONE_REQUEST_REFUSED.*#4*1##(trace-corpus.tsv): the SCS gateways expand it into four bus queries, dimension 0, 12, 14 and 13 ($0499013000,$0499013012,$049901300E,$049901300D), nack, out on F450, F453, F453AV, F454, F459, H4684, MH200, MH200N and ack, out on MH202; F460 and F461 send a single$00499013000and MyHomeServer1 one payload. Against a 99-zone central unit every query is answered (F454 +#0,tests/fixtures/traces/issue_629/home-assistant_myhome_2026-10-05T11-16-10.935Z.cleaned.loglines 489-700:*#4*1##..*#4*15##complete in ~1.3 s each with all five frames); a standalone thermostat answers only some, hence the NACK after 1.5-2 s. The 10 s socket hang of F455 multiple timeouts #674 was an F455 (not in the oracle) on actuator-only channels (*#4*Z*19*0*0##). That row puts F450 in thesupports_zone_statusexclusion tuple and keeps F460 out ofZONE_TEMPERATURE_POLL.*#4*1*0##(results/<P>/<V>/oracle/full/thermo-ts10.tsv, also intests/golden/firmware_oracle.json, the WHO 4 probe ofscripts/update_gateway_profiles.py): ack, out on MH202010024; nack, out with the bus payload93 01 14 00/$004990001000on the other eleven images (F450, F453, F453AV, F454, F459, F460, F461, H4684, MH200, MH200N, MyHomeServer1). The per-zone dimension 0 request this PR sends is forwarded everywhere; the NACK is the bus TX timeout of an image with no thermostat wired, identical to the live F461's unpopulated zone 1 (rows 374-376), while its populated zone 2 answers with an ACK in 119 ms. Until a command-session capture of*#4*Z*0##on an SCS-only gateway (F454 / MH200) exists, no_poll_answeredcallback is attached to it.*#16*0*5##(results/<P>/<V>/oracle/full/sound-who16-source.tsv): nack, silent on F450, F459, F460, F461, MH202, MyHomeServer1 (77 of 77 WHO 16 frames silent on each); ack, out on F453, F453AV, F454, H4684, MH200, MH200N. That row isSOUND_STATUS_REQUEST_REFUSED.#0/#23#1and#4#01coverage:*#4*2*0##out on every image (consistent with thethermo-ts10.tsvrow above),*#4*2#4#01*0##nack, silent on F450, F453, F453AV, F454, F459, F460, F461, MH200N, MH202 and MyHomeServer1 and forwarded on H4684 and MH200,*#4*2#4#01##a thermoregulation-daemon crash on the MH202. That isINTERFACE_ZONE_REQUEST_REFUSED; the gate fails closed (no poll) on the listed models and sends on the two that forward it and on the MH201, which is not in the oracle.Review follow-up (review 5642254816)
supports_zone_statusexclusion tuple, F460 leavesZONE_TEMPERATURE_POLL(now explicit) and the SCS-only list inclimate.md;runtime_behaviour.mdline 15 says which models poll*#4*<WHERE>##and which ask*#4*<WHERE>*0##;("F450", True)and("F460", False)added to the zone temperature poll table -> ede6721interface_zone_request_refused(profile flag first,INTERFACE_ZONE_REQUEST_REFUSEDfallback),_polls_temperatureoff for such zones on those models with one DEBUG line, the_poll_answeredomission commented,climate.mdqualified, tests -> 329ec0amyhome.sweep_buswithforce=Falseskipshandler._refused_by_gateway(q)frames,force=Truesends them; F461 test both ways, MH202 multi-gateway expectation 45 -> 43,services.yaml/services.md/ release notes -> 06afedfHL4684keyed asH4684in_model_keyand listed in thesupports_zone_statustuple; the OWNd-aliased spellings (L4684, NT4684, item numbers) follow their profile's model name insupports_zone_status(found while cross-checking the diff); tests -> 8dcd9bcGATEWAY_DISCOVERY_METADATAnotes for MH202 (no more "Sound (*#16*0*5##)"), F454, F461, MyHomeServer1, MH200,hardware-compatibility.mdregenerated -> a0ff59athermo-ts10.tsvrow*#4*1*0##cited directly, feat(oracle): add thermo-zone-requests suite (per-zone polls, actuators, humidity, interface form) own-firmware-oracle#60 kept for zone 2 /#0/#4#01, the draft blocker sentence replaced, the state diagram updated for the interface gate andsweep_bus.(The shas are those after the rebase on the current
v2-phase1-architecture.)Review follow-up (review 5642844379)
"MH201"added toGENERAL_ZONE_REQUEST_REFUSEDon theissue_466evidence (sweep frame 11*#4*0##with no zone frame, sibling trace zones 2, 3, 4);runtime_behaviour.mdlines 15 / 37,climate.md, the b16 release note and("MH201", False, True)intest_sweep_skips_requests_the_gateway_refuses; the tests whose default sweep model is an MH201 drop*#4*0##(the delegated-WHO 4 follower case moves to an MH200N) -> e4d8adcclimate.pycomments corrected: SCS gateways expand*#4*Z##into the four dimension queries (F454 +#0answers in ~1.3 s,issue_629; the F455 multiple timeouts #674 10 s hang was an F455 on actuator-only channels); the*#4*1*0##NACK is the unpopulated-zone bus TX timeout (live F461 zone 1 after 2.07 s) while the populated zone 2 answers in 119 ms;_poll_answeredstays off "until a command-session capture of*#4*Z*0##on an SCS-only gateway exists";*#4*Z*0##kept; feat(climate): hydrate cold subordinate zones via one-shot *#4*Z## once live #0 central unit is confirmed #813 named as the cold-start#0track; this body corrected the same way -> 3c2f5c9_polls_temperatureis a pure boolean property; the one-shot DEBUG line and_interface_poll_skip_loggedmoved intoasync_update, written when the poll is withheld (also when a fan / humidity request keeps the update going); tests assert nothing is logged by construction or by the property and once on update -> a4abfe6hardware-compatibility.md: the column is split rather than trimmed, becausecalibrate_discovery_with_owndrecomputesqueried_whosfrom the OWNd profile and the oracle validator requires the two to match; column 2 stays the profile's WHOs, column 3 is what the sweep sends (per-rowsweep_omits, validated, and checked against_discovery_frames()by a test); MH200 and MH200N split, the MH201 note (*#4*0##refusal + per-zone*#4*<WHERE>*0##) and the H4684 / HL4684 row added; regenerated withsync_documentation.py --update-> 2af4e8bservices.md:myhome.sweep_buswithforce: falsesends zero WHO 4 frames on F450, F454, F459, F460, F461, H4684, MH200, MH201, MH202 and MyHomeServer1 and triggers no per-zone poll;force: truesends them and the firmware drops them -> da8c29btests/test_startup_requests_per_firmware.pyloadsf461_live_2026-10-23(zone 2*#4*2*0*0239##, zone 1 NACK),myhomeserver1_live_2026-10-24(zones 35..70) and theissue_466MH201 sweep (frame 11*#4*0##with no reply) with the loaders the other trace tests use and replays the captured frames; the synthetic zone 3 cases stay and say so; the "Where are the traces" row ofrun6credits it with the bus-visible replies only and thetxomissions are attributed to the HA debug log of the live retest -> d3be161 + this bodyChecklist
What is the problem,What is the solution,State Diagram,How is it tested,Where are the traces,What does the Firmware Oracle / Encyclopedia say).*#4*Z*0##fallback: the publishedthermo-ts10.tsvrow and the live F461; the F422 interface gate: feat(oracle): add thermo-zone-requests suite (per-zone polls, actuators, humidity, interface form) own-firmware-oracle#60, unpublished, failing closed.)handle_event()and do not manually subscribe viaasync_dispatcher_connect.pytest,ruff, andscripts/verify_ha_standards.pypass.🤖 Generated with Claude Code