Skip to content

fix(climate): do not poll humidity (dimension 60) where the emulated firmware did not forward it - #793

Merged
GreenGrassBlueOcean merged 11 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/climate-humidity-poll-gate
Oct 25, 2026
Merged

GreenGrassBlueOcean merged 11 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/climate-humidity-poll-gate

Conversation

@gdluck

@gdluck gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown

Requested by A

What is the problem

A climate zone with humidity support asks *#4*Z*60## on every gateway after the startup sweep. In the firmware oracle's thermo-zone-requests suite (OpenWebNet-HA/own-firmware-oracle#60, merged 2026-10-25, findings/thermo-zone-requests.md on main: a firmware_observed verdict on emulated firmware) the emulated firmware of eight of the twelve images (F450, F453, F453AV, F454, H4684, MH200, MH200N, MH202) NACKed the request and put nothing on the bus, so there the poll only costs a command-session round trip and a NACK in the log, while the zone's own pushes keep the value fresh. This PR skips the humidity poll on those models until the OWNd profile says otherwise. Gateways whose emulated firmware forwarded the request (F459, F460, F461, MyHomeServer1), and models the oracle does not cover, are still polled; the live F461 and MyHomeServer1 answer it.

The table is an emulated-firmware verdict (findings/thermo-zone-requests.md; the per-gateway TSV rows reach oracle main with OpenWebNet-HA/own-firmware-oracle#63, a mirror of the CI commit on oracle/update-suites), not a physical trace on those eight models, so it stays a fallback behind the OWNd profile flag: the landing order is this consumer, then an OWNd supports_heating_humidity_request flag (None = unverified) that takes over the table.

Refs #712 (humidity queried at startup). No open issue is closed by this change.


What is the solution

  1. gateway.py: humidity_request_refused first asks _who4_target() (the helper of feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792, copied verbatim: _delegated_target(probe) or _failover_target(probe) or self on a fixed WHO 4 probe, the order send_status_request uses) and, when the request would go through another gateway (a secondary WHO 4 is delegated to, or the warm standby while this primary is down), returns that gateway's verdict. On the gateway itself it reads getattr(profile, "supports_heating_humidity_request", None): True / False wins; None or a profile without the attribute (every profile of the pinned OWNd) falls back to HUMIDITY_REQUEST_REFUSED (F450, F453, F453AV, F454, H4684, MH200, MH200N, MH202), the fix(gateway): close idle command sessions before the gateway drops them #381 pattern. The flag belongs in OWNd's profiles; no OWNd issue exists for it yet.
  2. climate.py: wants_humidity = self._humidity and humidity_request_refused is not True is computed once and used in the early-return guard of async_update and in the send branch, so a humidity zone on a refusing SCS gateway (no *#4*Z##, no fan) returns before _poll_health.should_skip instead of counting a poll that is never sent; a refused request is logged at DEBUG. _poll_on_add is unchanged (_enable_humidity still sets it, so the first update runs after the in-band identity settled). Mock gateways without the property, and real gateways not in the table, are unchanged.
  3. gateway.py: _model_key takes the key from a known OWNd profile's name and otherwise, for a GenericGatewayProfile, from the configured model string: a brand-prefixed alias or catalogue number is resolved word by word through OWNd's own aliases ("BTicino L4684" -> H4684, "Legrand 003598" -> F454, "Legrand 003565" -> MH200N), the SSDP spelling HL4684 (manifest.json; an SSDP identity is never overwritten by WHO 13 / 1013) is read as H4684 the way fix(gateway): skip the startup requests the firmware refuses, poll each zone's temperature on SCS-only gateways #794 does it, and otherwise a known model is looked up as a substring, longest first, so MH200N stays distinct from MH200. The lookup is keyed on KNOWN_GATEWAY_MODELS (the twelve oracle images and the other models OWNd profiles), so _model_key of "BTicino F461" is "F461", the value feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792's tables give it.
  4. The code comments, docs/configuration/climate.md and the b16 release-notes delta say "the emulated firmware did not forward it (own-firmware-oracle findings/thermo-zone-requests.md, a firmware_observed verdict)", not that a live gateway refuses it.

Shared hunks and landing order. _who4_target() is #792's hunk, unchanged. _model_key is a superset of #792's (the GenericGatewayProfile fallback and the longest-first substring lookup) and of #794's (HL4684 -> H4684, aliases through get_gateway_profile(...).model_name); _ZONE_REQUEST_MODELS is built from KNOWN_GATEWAY_MODELS | HUMIDITY_REQUEST_REFUSED here and from ACTUATOR_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS) in #792, so both give the same key for every model named in either table. The three PRs add their tables at the same place in gateway.py (and #792 / #793 touch the same release-notes line): whichever of #792 / #793 / #794 lands first, the others rebase and keep one _model_key / _ZONE_REQUEST_MODELS / _who4_target.


Companion PR: none yet. The OWNd profile flag supports_heating_humidity_request (None = unverified) should land in OWNd after oracle OpenWebNet-HA/own-firmware-oracle#60; this PR reads it forward-compatibly.


State Diagram

stateDiagram-v2
    direction TB

    state "1. Before" as Before {
        [*] --> Asked : sweep done, zone with humidity polls\n*#4*Z## then *#4*Z*60## on every gateway
    }

    state "2. After: humidity_request_refused" as After {
        [*] --> Gate : sweep done, zone polls *#4*Z##
        Gate --> Asked : profile flag True\nor flag None or absent and model not in HUMIDITY_REQUEST_REFUSED
        Gate --> Skipped : profile flag False\nor flag None or absent and model in the table , DEBUG
        Asked --> Fresh : *#4*Z*60## answered *#4*Z*60*HH##
        Skipped --> Fresh : the probes own push *#4*Z*60*HH##
    }
Loading

The gate is evaluated on the gateway send_status_request routes WHO 4 through (a delegated secondary, or the warm standby while the primary is down), not on the primary bound to the entity.


Follow-up (oracle #60 merged)

  • 290e4bb: comments, docs, release note and the test docstring cite findings/thermo-zone-requests.md on oracle main instead of an unpublished run; no behaviour change. Rebased on the current base.

Review follow-up (review 5644219403)

  1. 946abbd humidity_request_refused follows WHO 4 delegation and warm-standby failover through _who4_target() (feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792's helper, verbatim); 13 cases in both directions, two through the real topology lookup. 13 fail before.
  2. bce1d0d _ZONE_REQUEST_MODELS keyed on KNOWN_GATEWAY_MODELS (the twelve oracle images and the other OWNd-profiled models) united with the refusing table; assert handler._model_key == "F461" for "BTicino F461", every known model checked behind a brand prefix. 9 of the 17 brand-prefix cases fail on the previous lookup.
  3. 1d1be7d SSDP HL4684 / HC4684 and brand-prefixed or catalogue strings ("BTicino L4684", "Legrand 003598", "Legrand 003565") resolved before the table lookup, MH200N kept distinct from MH200; fix(gateway): skip the startup requests the firmware refuses, poll each zone's temperature on SCS-only gateways #794's two _model_key tests copied as they are. 12 fail before.
  4. a70026c wants_humidity in the early-return guard of async_update: a refusing gateway's humidity zone never reaches _poll_health; an F450 (refuses dimension 60, answers *#4*Z##) still sends and counts its zone poll. 3 fail before.
  5. 51aee89 the captured humidity frames are replayed through MyHOMEClimate (five cases, loaders like the other trace modules); this description: checklist item 2 checked, the draft wording removed, the oracle TSV location stated.

Review follow-up (reviews 5644470090 and 5644539748)

  1. Rebase after feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792 (whole branch replayed on 2ece474, git merge-tree clean): one _model_key (this PR's KNOWN_GATEWAY_MODELS / _MODEL_SPELLINGS / _resolved_model superset now serves actuator_request_refused and all_zone_request too), one _who4_target(), _ZONE_REQUEST_MODELS = sorted(KNOWN_GATEWAY_MODELS | ACTUATOR_REQUEST_REFUSED | HUMIDITY_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS), key=len, reverse=True), the WHO 4 release-note bullet carries both clauses. feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792's tests/test_climate_actuator_status.py passes unchanged on the result.
  2. Tokeniser (28dfe04): _model_key reads the alphanumeric runs of the string (re.findall(r"[A-Za-z0-9]+", …)), so "BTicino-L4684" → H4684 and "F454 (003598)" → F454; tests.
  3. Wording (5c9c9e1): climate.py, docs/configuration/climate.md and this description no longer claim the eight gateways push humidity upward; they say that any *#4*<WHERE>*60*<HH>## event the gateway delivers still updates current_humidity without polling.
  4. Trace fidelity (b0bc3d1): the test that fed F461 pushes to an MH202 handler is gone from the captured-traces section; each capture is replayed on its own gateway through the climate platform (table above), the F455 issue_674 push included.
  5. Oracle TSV publication: oracle: publish the thermo-zone-requests, who1-motion-detector and who13-clock suite results (mirror of oracle/update-suites d3767f49) own-firmware-oracle#63 mirrors the CI commit of oracle/update-suites (d3767f49) so the thermo-zone-requests.tsv rows and results/mcp_index.json reach main.

Hardware Verification

  • F461 firmware 2.0.11, 2026-10-23: *#4*2*60## answered with *#4*2*60*39## in 0.14 s, *#4*0*60## answered by zone 2 after 2.1 s; the thermostat pushed *#4*2*60*38## three times in 30 minutes on its own. The F461 is outside the table and keeps its poll.
  • MyHomeServer1 firmware 3.87.13 (the oracle emulates 2.82.06), 2026-10-24: *#4*39*60## -> *#4*39*60*51## (0.2 s); *#4*0*60## ACKed in 0.5 s and answered by 17 zones; *#4*35*60## NACKs after 2.1 s. Zone 35 never emits dimension 60 in the capture while 17 other zones do, so "no humidity probe in zone 35" is the likely reason, an inference, not a captured fact. MyHomeServer1 is outside the table; the gate is per model, not per zone, so unprobed zones still time out on gateways that forward the request.
  • No physical gateway of the eight listed models was captured: the negative side of the table is emulator-only.

Live retest (2026-10-24)

Consensus label: gate not exercised. The F461 is outside the refused set: at startup tx *#4*2*60## -> rx *#4*2*60*41##, current_humidity: 41.0. That is a negative control (the request is still sent where it should be), not a test of the skip.


How is it tested

  • tests/test_climate_humidity_poll_gate.py (97 cases). The original 36: the fallback table for 15 models (including F455, MH201 and Generic, which are not in the oracle and are still asked); the profile flag True / False overriding the table both ways; None = unverified (×5) and a profile without the attribute (×2) falling back to the table; an F455 zone with the flag None still sends *#4*2*60##; an F461 zone still sends *#4*2##, *#4*2*60##; F454 and MH202 zones send nothing; a gateway without the property is still asked; _model_key for BTicino F461, Legrand MH200N, MH200, BTicino F453AV, Unknown, Generic, and a Legrand MH200N zone sending nothing.
  • Review follow-up (61 new cases): WHO 4 delegation to a forwarding / refusing secondary and failover to a refusing / forwarding standby, both directions (F461 / MyHomeServer1 against MH202 / F454), the secondary preferred to the standby, the standalone gateway unchanged, a configured secondary and a configured standby through the real topology lookup (13); every known model behind a brand prefix keyed as itself (17) and the oracle's twelve images all known (1); fix(gateway): skip the startup requests the firmware refuses, poll each zone's temperature on SCS-only gateways #794's test_aliased_model_strings_follow_their_profile (×4) and test_ssdp_model_string_hl4684_is_keyed_as_h4684, eleven brand-prefixed / catalogue / SSDP strings and two H4684 humidity zones (HL4684, BTicino L4684) not asking for humidity (18); _poll_health never reached on a refusing SCS gateway (×3), the F450 zone poll still counted, the F461 zone through _poll_health and held back when it skips, _poll_on_add after _enable_humidity (7); the captured frames (5, below).
  • Fail-before / pass-after: 26 of the original 29 fail on the unpatched base; 3 of the 7 _model_key cases fail on the helper before 7ec1f1f; of the new cases 13 (delegation), 9 (brand-prefixed forwarding models), 12 (SSDP / aliases) and 3 (_poll_health) fail on the code before each commit; all 97 pass with the change.
  • Full suite against the PyPI-pinned OWNd behaviour (plain upstream OWNd master, f04d8aa, on PYTHONPATH): 4761 passed, 1 skipped, 100.0 % statement coverage (16358 statements), on v2-phase1-architecture 2ece474 (after feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792).
  • ruff check . (0.17.2 and 0.16.7), 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). Physical traces exist for the positive side only.

Subsystem Capture Plant Frames verified by this PR
WHO 4 dimension 60 on a forwarding gateway tests/fixtures/traces/f461_live_2026-10-23/myhome_trace_F461_live_2026-10-23.json, runs run2_reviewer_reads (requests) and run6_event_session_ha_fixed_integration (pushes) F461 2.0.11, zone 2 *#4*2*60## -> *#4*2*60*39## (0.14 s); *#4*0*60## -> *#4*2*60*39## (2.1 s); spontaneous *#4*2*60*38## ×3
Per-zone and general humidity on a second gateway family tests/fixtures/traces/myhomeserver1_live_2026-10-24/myhome_trace_MyHomeServer1_live_2026-10-24.json, runs run2_reads_thermo_sound_alarm_lights and run3_reads_lights_addresses_humidity MyHomeServer1 3.87.13 *#4*35*60## NACK 2.1 s; *#4*0*60## ACK 0.5 s with 17 zone replies (*#4*39*60*51## … *#4*70*60*69##); *#4*39*60## -> *#4*39*60*51##
The eight models in the fallback table no physical trace: emulator only (own-firmware-oracle findings/thermo-zone-requests.md, merged in OpenWebNet-HA/own-firmware-oracle#60) — —

tests/test_climate_humidity_poll_gate.py replays each capture on its own gateway model through climate.async_setup_entry and myhome_message_<mac> (the pattern of tests/test_climate_actuator_status.py): F461 run6 spontaneous *#4*2*60*38## pushes enable humidity and the zone is then asked, F461 run2 *#4*2*60## → *#4*2*60*39##; MyHomeServer1 run2 zone 35 *#4*35*60## NACK and the *#4*0*60## 17-zone burst, run3 zone 39 *#4*39*60## → *#4*39*60*51##; and the F455 of tests/fixtures/traces/issue_674/myhome_trace_f455_climate_timeouts_2026-10-09.json, an SCS gateway outside the table, whose spontaneous *#4*42*60*52## push updates the zone while it is still polled. No capture of one gateway is presented as another's traffic; the MH202 case (a delivered event enables humidity, the poll stays off) is a unit test of integration-side delivery.


What does the Firmware Oracle / Encyclopedia say

  1. OpenWebNet Encyclopedia (functional/who-4-temperature-control/dimensions.md): dimension 60 is not in the published functional DIMENSION table (0, 11, 12, 13, 14, 19, 20, 22, 30, 31); humidity appears only in the historical BACnet air-handling-unit record. The request and its *#4*Z*60*HH## reply are known from captures (feat(climate): remember zone humidity support and query dimension 60 on startup (#710) #712 and the two plants above), not from the specification.
  2. Firmware Oracle: findings/thermo-zone-requests.md (feat(oracle): add thermo-zone-requests suite (per-zone polls, actuators, humidity, interface form) own-firmware-oracle#60, merged 2026-10-25): *#4*2*60## and *#4*0*60## nack, silent on F450 020010, F453 010100, F453AV 030014, F454 020051, H4684 020054, MH200 020102, MH200N 010108, MH202 010024; nack, out on F459 020105, F460 020012, F461 020011, MyHomeServer1 028206. The per-gateway rows results/<P>/<V>/oracle/full/thermo-zone-requests.tsv and the regenerated results/mcp_index.json were produced by the suites.yml run for 52d354bb and pushed to the oracle branch oracle/update-suites (commit d3767f49); they reach main with the maintainers' next suites PR, so no published TSV row on main covers dimension 60 yet (none at 53f9514). The oracle runs emulated firmware with no probe behind it, so it shows whether the request reaches the bus, not whether a probe answers or pushes.

Checklist

  • PR Description: Follows the structured sections above (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).
  • Trace-Backed (if protocol/device change): Behavior is verified against real OpenWebNet bus traces or gateway firmware fixtures rather than guessed. (The positive side replays the committed F461 and MyHomeServer1 captures; the eight-model table rests on the merged oracle finding, emulated firmware, behind the OWNd profile flag.)
  • V2 Architecture: New or modified entities implement handle_event() and do not manually subscribe via async_dispatcher_connect.
  • Tests & Linters: pytest, ruff, and scripts/verify_ha_standards.py pass.

🤖 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 pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 24, 2026
… 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>
@gdluck
gdluck force-pushed the fix/climate-humidity-poll-gate branch from 6850fac to 2bc1fd4 Compare October 24, 2026 15:54
@gdluck gdluck changed the title fix(climate): do not poll humidity (dimension 60) on gateways that drop the request fix(climate): do not poll humidity (dimension 60) where the emulated firmware did not forward it Oct 24, 2026
@gdluck
gdluck force-pushed the fix/climate-humidity-poll-gate branch from 2bc1fd4 to d8b3db7 Compare October 24, 2026 18:05
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck this url link is hallucinated oracle PR #60)

@gdluck

gdluck commented Oct 24, 2026

Copy link
Copy Markdown
Author

You are right, that was a bare #60 meant for the oracle repository, which GitHub turned into a link to this repository's issue 60. Every oracle, OWNd and openwebnet-mcp reference in the open PR descriptions of both repositories now uses the qualified owner/repo#number form (for this one: OpenWebNet-HA/own-firmware-oracle#60), and I checked my earlier comments for the same slip.

gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 24, 2026
…he WHO 4 target gateway (OpenWebNet-HA#792 review, 5)

Four edge cases of the per-model tables and the zone watch:

- _model_key compared the profile's model_name with "Generic" instead of its
  class: get_gateway_profile("BTicino F461") is a GenericGatewayProfile named
  "BTicino F461", so the key became "BTICINOF461" and missed both
  ALL_ZONE_REQUESTS and ACTUATOR_REQUEST_REFUSED. The key is now taken from a
  known profile's name, otherwise from the configured model, and a known model
  is looked up as a substring (longest first), like command_session_limit and
  supports_zone_status do.
- The refusal and the all-zone form were read from this handler while
  send_status_request routes WHO 4 through the delegated secondary or the warm
  standby. _who4_target() resolves the same target (_delegated_target /
  _failover_target) and its tables decide: a primary F461 failed over to an
  MH202 sends nothing, failed over to an F454 asks per zone; an MH202
  delegating WHO 4 to an F454 asks the F454.
- A secondary probe (WHERE >= 100, push-only; *michnovka#4*105*...## NACKs in 2.1 s on
  the F461, docs/configuration/climate.md) no longer registers a zone watch.
- MyHomeServer1 gets the all-zone form: in myhomeserver1_live_2026-10-24
  (3.87.13, run2_reads_thermo_sound_alarm_lights) *michnovka#4*#0*20## was ACKed in
  0.39 s with 43 actuator frames for 34 zones, where the per-zone requests take
  about 32 s through the paced sender; the emulated 028206 forwards it ("out").

Review 5639409551 on OpenWebNet-HA#792, finding 5. Draft OpenWebNet-HA#793 carries the same _model_key
helper and needs the same change.

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

Copy link
Copy Markdown
Contributor

@gdluck we will keep this in draft until the oracle outcome can be attached to this pr

@gdluck

gdluck commented Oct 24, 2026

Copy link
Copy Markdown
Author

Agreed, it stays draft until OpenWebNet-HA/own-firmware-oracle#60 is in and the published rows can be cited here. The branch is meanwhile kept rebased and aligned with #792 (shared _model_key helper).

@gdluck
gdluck force-pushed the fix/climate-humidity-poll-gate branch from d8b3db7 to 7ab740c Compare October 24, 2026 19:23
gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 24, 2026
…he WHO 4 target gateway (OpenWebNet-HA#792 review, 5)

Four edge cases of the per-model tables and the zone watch:

- _model_key compared the profile's model_name with "Generic" instead of its
  class: get_gateway_profile("BTicino F461") is a GenericGatewayProfile named
  "BTicino F461", so the key became "BTICINOF461" and missed both
  ALL_ZONE_REQUESTS and ACTUATOR_REQUEST_REFUSED. The key is now taken from a
  known profile's name, otherwise from the configured model, and a known model
  is looked up as a substring (longest first), like command_session_limit and
  supports_zone_status do.
- The refusal and the all-zone form were read from this handler while
  send_status_request routes WHO 4 through the delegated secondary or the warm
  standby. _who4_target() resolves the same target (_delegated_target /
  _failover_target) and its tables decide: a primary F461 failed over to an
  MH202 sends nothing, failed over to an F454 asks per zone; an MH202
  delegating WHO 4 to an F454 asks the F454.
- A secondary probe (WHERE >= 100, push-only; *michnovka#4*105*...## NACKs in 2.1 s on
  the F461, docs/configuration/climate.md) no longer registers a zone watch.
- MyHomeServer1 gets the all-zone form: in myhomeserver1_live_2026-10-24
  (3.87.13, run2_reads_thermo_sound_alarm_lights) *michnovka#4*#0*20## was ACKed in
  0.39 s with 43 actuator frames for 34 zones, where the per-zone requests take
  about 32 s through the paced sender; the emulated 028206 forwards it ("out").

Review 5639409551 on OpenWebNet-HA#792, finding 5. Draft OpenWebNet-HA#793 carries the same _model_key
helper and needs the same change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/climate-humidity-poll-gate branch from 7ab740c to 290e4bb Compare October 25, 2026 10:44
gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 25, 2026
…he WHO 4 target gateway (OpenWebNet-HA#792 review, 5)

Four edge cases of the per-model tables and the zone watch:

- _model_key compared the profile's model_name with "Generic" instead of its
  class: get_gateway_profile("BTicino F461") is a GenericGatewayProfile named
  "BTicino F461", so the key became "BTICINOF461" and missed both
  ALL_ZONE_REQUESTS and ACTUATOR_REQUEST_REFUSED. The key is now taken from a
  known profile's name, otherwise from the configured model, and a known model
  is looked up as a substring (longest first), like command_session_limit and
  supports_zone_status do.
- The refusal and the all-zone form were read from this handler while
  send_status_request routes WHO 4 through the delegated secondary or the warm
  standby. _who4_target() resolves the same target (_delegated_target /
  _failover_target) and its tables decide: a primary F461 failed over to an
  MH202 sends nothing, failed over to an F454 asks per zone; an MH202
  delegating WHO 4 to an F454 asks the F454.
- A secondary probe (WHERE >= 100, push-only; *michnovka#4*105*...## NACKs in 2.1 s on
  the F461, docs/configuration/climate.md) no longer registers a zone watch.
- MyHomeServer1 gets the all-zone form: in myhomeserver1_live_2026-10-24
  (3.87.13, run2_reads_thermo_sound_alarm_lights) *michnovka#4*#0*20## was ACKed in
  0.39 s with 43 actuator frames for 34 zones, where the per-zone requests take
  about 32 s through the paced sender; the emulated 028206 forwards it ("out").

Review 5639409551 on OpenWebNet-HA#792, finding 5. Draft OpenWebNet-HA#793 carries the same _model_key
helper and needs the same change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck marked this pull request as ready for review October 25, 2026 11:24
@gdluck

gdluck commented Oct 25, 2026

Copy link
Copy Markdown
Author

OpenWebNet-HA/own-firmware-oracle#60 is merged (main 52d354b): the table's source is now findings/thermo-zone-requests.md and the comments, docs, release note and test docstring cite it (290e4bb, no behaviour change). All checks green; marking ready for review.

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

Audit of PR #793 (290e4bb7)

Branch / Base: gdluck:fix/climate-humidity-poll-gate (290e4bb7) → v2-phase1-architecture (089fef51) — 5 files (+217 / -2)
Verification Run:

  • GitHub CI (290e4bb7): 17 / 17 check-runs green (OWNd pinned/latest/dev, pytest + coverage gate, mypy-ratchet, validate, HA Container & Compatibility stable/beta/dev, quality_scale.yaml).
  • Local WSL Suite (Python 3.14.7, HA 2026.10.1, OWNd==2.0.0b11 & editable OWNd): ruff check, scripts/verify_ha_standards.py, scripts/sync_documentation.py --check, and scripts/typing_ratchet.py (mypy --strict: 0 error(s)) all pass; tests/test_climate_humidity_poll_gate.py passes 36 / 36 against both pinned OWNd==2.0.0b11 and OWNd master with 100% diff statement coverage.
  • Protocol & Trace Verification: Verified against the OpenWebNet Encyclopedia (ownkb-build-0.9.0: WHO 4 public DIMENSION table ownkb:chunk:r000573 + Living Now KW4691 / F459 / F460 humidity timeline ownkb:claim:c047609, c009734), OpenWebNet-HA/own-firmware-oracle#60 (52d354bb), and the committed f461_live_2026-10-23 / myhomeserver1_live_2026-10-24 captures. Unemulated gateways (F455, MH201, Generic) are conservatively excluded from HUMIDITY_REQUEST_REFUSED, and supports_heating_humidity_request overrides the fallback table when isinstance(..., bool).

Changes Requested (Ordered by Severity)

1. [Medium — Bug] humidity_request_refused ignores shared-bus WHO 4 delegation and warm-standby failover

  • Where: custom_components/myhome/gateway.py:373-381, custom_components/myhome/climate.py:562, vs MyHOMEGatewayHandler.send_status_request (gateway.py:672-680).
  • What happens: climate.py checks self._gateway_handler.humidity_request_refused on the primary gateway bound to the entity, whereas send_status_request routes WHO 4 frames through _delegated_target / _failover_target:
    1. False suppression: Primary MH202 / F454 delegating WHO=4 to secondary F461 / MyHomeServer1 (or failing over to warm standby F461) evaluates humidity_request_refused == True on self and skips *#4*Z*60##, even though send_status_request would send through F461 / MyHomeServer1.
    2. False poll: Primary F461 / MyHomeServer1 failing over to warm standby MH202 / F454 evaluates humidity_request_refused == False on self and sends *#4*Z*60## into the standby MH202 / F454, which NACKs it.
  • Recommendation: Route humidity_request_refused through the same _who4_target() helper added in #792 (target = self._who4_target(); if target is not self: return target.humidity_request_refused) and add delegation + warm-standby failover test cases in both directions.

2. [Medium — Sibling PR Conflict & Divergent _model_key] _ZONE_REQUEST_MODELS differs from #792 and conflicts on merge

  • Where: custom_components/myhome/gateway.py:162, 357-370 and .github/release-notes/2.0.0b16.md:34.
  • What happens:
    • Commit 290e4bb7 and the PR description state that #792 and #793 "no longer differ in what _model_key returns", which is inaccurate: #793 builds _ZONE_REQUEST_MODELS from HUMIDITY_REQUEST_REFUSED alone (has F450, F454; lacks F461, MYHOMESERVER1), whereas #792 builds it from ACTUATOR_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS) (has F461, MYHOMESERVER1; lacks F450, F454).
    • On #793, _handler(hass, "BTicino F461")._model_key returns "BTICINOF461" rather than "F461", contradicting _model_key's own docstring ("""The gateway model the request tables are keyed on (F461, MYHOMESERVER1).""").
    • git merge-tree --write-tree between #792 (4a41da61) and #793 (290e4bb7) conflicts in both custom_components/myhome/gateway.py and .github/release-notes/2.0.0b16.md.
  • Recommendation: Key _ZONE_REQUEST_MODELS off the full set of known gateway models (including forwarding models such as F461, F459, F460, MYHOMESERVER1) so _model_key actually returns identical values across both PRs, and coordinate/rebase once the first of #792 / #793 lands.

3. [Low — Edge Case] SSDP HL4684 and vendor-prefixed catalog aliases miss _model_key fallback

  • Where: custom_components/myhome/gateway.py:369-370, custom_components/myhome/manifest.json:25, custom_components/myhome/const.py:502-526.
  • What happens:
    • manifest.json matches SSDP "modelName": "HL4684", while OWNd.profiles.get_gateway_profile("HL4684") (and vendor-prefixed alias/catalog strings like "BTicino L4684", "Legrand 003598" for F454, "Legrand 003565" for MH202, "Legrand 003535" for F453AV) returns GenericGatewayProfile.
    • Because "H4684" is not a contiguous substring of "HL4684" or "BTICINOL4684", _model_key evaluates to "HL4684" / "BTICINOL4684" and misses HUMIDITY_REQUEST_REFUSED — and because SSDP is a technical identity, resolve_gateway_identity (identity.py:13-15) never overwrites modelName when WHO=13/1013 arrives.
  • Recommendation: Normalize HL4684 / L4684 / HC4684 → H4684 (and brand/catalog aliases via _SAME_PRODUCT in identity.py, without collapsing MH200N → MH200) in _model_key, and add a test for "HL4684" / "BTicino L4684".

4. [Low — Code & PR Hygiene]

  1. Early-exit guard in MyHOMEClimate.async_update (custom_components/myhome/climate.py:529-546 vs :562):
    • not self._humidity at line 533 still keeps async_update() running on MH202 / F454 even when humidity_request_refused is True (and not self._central, not self._gateway_supports_zone_status, not self._fan), causing self._poll_health.should_skip(...) at line 543 to run and potentially raise/log on a zone that was never going to send a single status request. Factoring wants_humidity = self._humidity and getattr(self._gateway_handler, "humidity_request_refused", False) is not True into the early return avoids entering _poll_health when no poll will be sent (while keeping _poll_on_add = True in __init__ / _enable_humidity so startup identity resolution before _async_poll_after_discovery is preserved).
  2. Oracle TSV publication & PR body cleanup:
    • On OpenWebNet-HA/own-firmware-oracle, the suites.yml run for 52d354bb (#40185238079) completed and pushed commit d3767f49 (results/<P>/<V>/oracle/full/thermo-zone-requests.tsv + regenerated results/mcp_index.json) to branch oracle/update-suites, which still needs a PR to main before the published TSVs and MCP index are on main.
    • Under ## Checklist, item 2 is still unchecked (- [ ]) and still says "that is why this PR is a draft".
    • Under ## Where are the traces, the PR states "The frames are also inlined in tests/test_climate_humidity_poll_gate.py", whereas the test file only asserts outgoing mock strings (*#4*2*60##) and never replays *#4*2*60*39##, *#4*2*60*38##, or *#4*39*60*51## from the committed captures. Either replay the captured humidity frames through MyHOMEClimate or adjust the wording in Where are the traces.

supported = getattr(getattr(self.gateway, "profile", None), "supports_heating_humidity_request", None)
if isinstance(supported, bool):
return not supported
return self._model_key in HUMIDITY_REQUEST_REFUSED

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.

humidity_request_refused inspects self directly, whereas send_status_request (gateway.py:672-680) dispatches WHO 4 requests through _delegated_target and _failover_target.

If a primary MH202 / F454 delegates WHO=4 to a secondary F461 / MyHomeServer1 (or fails over to warm standby F461), this returns True and suppresses humidity polling even though the request would be sent through F461. Conversely, if primary F461 fails over to standby MH202, this returns False and sends *#4*Z*60## into the refusing standby.

Please route this through the same _who4_target() helper as #792:

target = self._who4_target()
if target is not self:
    return target.humidity_request_refused

and add test coverage for both delegation and warm-standby failover directions.

Comment thread custom_components/myhome/gateway.py Outdated
# wins, ``None`` = unverified); this table is the fallback until the flag exists there.
HUMIDITY_REQUEST_REFUSED = frozenset({"F450", "F453", "F453AV", "F454", "H4684", "MH200", "MH200N", "MH202"})
# The models the table knows, longest first so ``MH200N`` is not read as ``MH200``.
_ZONE_REQUEST_MODELS = sorted(HUMIDITY_REQUEST_REFUSED, key=len, reverse=True)

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.

_ZONE_REQUEST_MODELS here only contains HUMIDITY_REQUEST_REFUSED, so _model_key does not return the same value as #792 (e.g. "BTicino F461" returns "BTICINOF461" here vs "F461" in #792, contradicting line 358's docstring, while #792 lacks F450 and F454). This line also merge-conflicts directly with #792.

Include the known forwarding models (F459, F460, F461, MYHOMESERVER1) or union all known gateway model keys so _model_key behaves identically in both PRs.

else:
name = self.model
normalized = "".join(character for character in str(name).upper() if character.isalnum())
return next((model for model in _ZONE_REQUEST_MODELS if model in normalized), normalized)

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.

manifest.json (line 25) advertises SSDP "modelName": "HL4684", for which get_gateway_profile("HL4684") (as well as vendor-prefixed strings like "BTicino L4684") returns GenericGatewayProfile. Because "H4684" is not a substring of "HL4684" or "BTICINOL4684" — and SSDP technical identities are never overwritten by resolve_gateway_identity — an SSDP-discovered HL4684 / BTicino L4684 misses HUMIDITY_REQUEST_REFUSED.

Please normalize HL4684 / L4684 / HC4684 (and _SAME_PRODUCT brand variants, keeping MH200N distinct from MH200) before the table lookup.

Comment thread custom_components/myhome/climate.py Outdated
cast(OWNCommand, OWNHeatingCommand.parse(f"*#4*{self._full_where}*11##"))
)
if self._humidity:
if getattr(self._gateway_handler, "humidity_request_refused", False) is True:

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.

Because the early-return guard at lines 529-534 checks not self._humidity unconditionally, a humidity-enabled zone on MH202 / F454 (_gateway_supports_zone_status == False, _fan == False) still runs self._poll_health.should_skip(...) at line 543 even when humidity_request_refused is True and zero frames will be sent.

Computing wants_humidity = self._humidity and getattr(self._gateway_handler, "humidity_request_refused", False) is not True and using not wants_humidity in the early-return guard avoids entering _poll_health when async_update() has nothing to send.

assert isinstance(handler.gateway.profile, GenericGatewayProfile)
# HUMIDITY_REQUEST_REFUSED names no forwarding model, so the configured name is kept (#792's
# all-zone table adds F461 to the lookup); either way the key is not a refusing model.
assert "F461" in handler._model_key

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.

Here handler._model_key is actually "BTICINOF461" (not "F461"), which is why assert "F461" in handler._model_key had to be weakened from == "F461" — directly contradicting commit 290e4bb7's claim that #792 and #793 return the same _model_key. Once _ZONE_REQUEST_MODELS includes the forwarding models, this can assert handler._model_key == "F461" cleanly; please also add cases for WHO 4 delegation/failover and "HL4684" / "BTicino L4684".

@gdluck

gdluck commented Oct 25, 2026

Copy link
Copy Markdown
Author

Thanks for the audit; all items are on the branch (head 51aee89, one commit per point).

  1. Delegation / failover (946abbd): humidity_request_refused now asks _who4_target() (feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792's helper, copied verbatim so the hunk merges clean) and returns the delegated secondary's or the warm standby's verdict when that is not self. Thirteen new cases cover both directions (MH202 → F461 and F454 → MyHomeServer1 delegation polled, the reverse not; failover to a refusing standby not polled, to a forwarding one polled; secondary preferred to standby; standalone unchanged), two of them through the real topology lookup; 13 fail before.
  2. _ZONE_REQUEST_MODELS (bce1d0d): keyed on every known gateway model (the twelve oracle images plus F452, F452V, F455, H4890, MH201), so _model_key("BTicino F461") == "F461" as in feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792 and the test asserts exactly that; the hunk cannot be byte-identical to feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792's because its source tables do not exist here, but the keys agree for every model either PR names. Landing order: whichever of feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792 / fix(climate): do not poll humidity (dimension 60) where the emulated firmware did not forward it #793 lands first, the other rebases (stated in the body).
  3. SSDP and brand aliases (1d1be7d): the words of a Generic model string are resolved through OWNd's get_gateway_profile (L4684 / NT4684 → H4684, 003598 → F454, 003565 → MH200N, 003535 → MH202), HL4684 / HC4684 map to H4684 (the same mapping as fix(gateway): skip the startup requests the firmware refuses, poll each zone's temperature on SCS-only gateways #794, whose two _model_key tests are copied as they are), then the longest-first lookup keeps MH200N distinct from MH200. HL4684 and BTicino L4684 humidity zones do not ask for dimension 60; 12 cases fail before. One note: *#4*Z## on an HL4684 is still sent on this branch, because supports_zone_status only learns that spelling in fix(gateway): skip the startup requests the firmware refuses, poll each zone's temperature on SCS-only gateways #794.
  4. Early return (a70026c): wants_humidity is in the guard and the send branch of async_update; a refusing gateway's humidity zone never enters _poll_health (asserted with should_skip raising if called), the F450 case that refuses 60 but answers *#4*Z## still sends and counts, _poll_on_add unchanged.
  5. Body and replay (51aee89): the captured *#4*2*60*39## and *#4*2*60*38## (F461) and *#4*39*60*51## (MyHomeServer1) frames are replayed through MyHOMEClimate from the committed traces (a push on a zone bound to a refusing MH202 enables and refreshes humidity with no poll); checklist item 2 is checked and the draft wording removed; the oracle TSV rows are located on oracle/update-suites (d3767f49), pending the next suites PR to main.

4656 passed / 1 skipped at 100 % coverage; ruff (0.16.7 and 0.17.2), typing ratchet, HA standards, docs sync and translations clean; test_climate_humidity_poll_gate.py 97 cases (36 before). An independent second-opinion review of the diff noted that a fan-only zone with nothing to send still reaches _poll_health; that path predates this PR (it is the fan branch), so it is left for a follow-up if you want it.

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

Audit of PR #793 (51aee891)

Branch / Base: gdluck:fix/climate-humidity-poll-gate (51aee891) → v2-phase1-architecture (2ece4744, post-#792)
Verification Run:

  • Review 5644219403 Follow-Up Verified (946abbd7 .. 51aee891):
    1. humidity_request_refused routes through _who4_target(), covering shared-bus WHO 4 delegation and warm-standby failover in both directions (946abbd7).
    2. KNOWN_GATEWAY_MODELS, _MODEL_SPELLINGS, and _resolved_model resolve SSDP HL4684/HC4684, brand-prefixed names ("BTicino F461" → "F461", "BTicino L4684" → "H4684"), and catalog numbers (003598 → F454, 003565 → MH200N, 003535 → MH202) without collapsing MH200N into MH200 (bce1d0d0, 1d1be7d5).
    3. wants_humidity in MyHOMEClimate.async_update's early-return guard avoids entering _poll_health.should_skip(...) on refusing SCS gateways when nothing will be sent, while keeping F450's *#4*Z## poll counted and _poll_on_add intact (a70026ca).
  • Local WSL Suite (Python 3.14.7, HA 2026.10.1, pinned OWNd==2.0.0b11): ruff check, scripts/verify_ha_standards.py (54/54 rules), scripts/sync_documentation.py --check, scripts/manage_translations.py check, scripts/typing_ratchet.py (mypy --strict: 0 error(s)), and pytest tests/test_climate_humidity_poll_gate.py tests/test_trace_oracle_validation.py (168 passed) all pass with 100.0% diff statement coverage on climate.py and gateway.py.

Changes Requested Before Merge (Ordered by Severity)

1. [Blocker — Merge Conflict] Rebase onto v2-phase1-architecture (2ece4744, post-#792)

  • Where: custom_components/myhome/gateway.py and .github/release-notes/2.0.0b16.md:34.
  • What happens: Now that #792 (2ece4744) is merged, #793 is DIRTY (mergeable: false) in both files:
    • gateway.py: ACTUATOR_REQUEST_REFUSED, ALL_ZONE_REQUESTS, _ZONE_REQUEST_MODELS, _model_key, and _who4_target().
    • .github/release-notes/2.0.0b16.md: line 34 (- **Thermoregulation (WHO 4)**:).
  • Recommendation: Rebase onto v2-phase1-architecture, keep a single _model_key (retaining #793's KNOWN_GATEWAY_MODELS / _MODEL_SPELLINGS / _resolved_model superset so actuator_request_refused and all_zone_request benefit from the same alias normalization) and a single _who4_target(), union _ZONE_REQUEST_MODELS = sorted(KNOWN_GATEWAY_MODELS | ACTUATOR_REQUEST_REFUSED | HUMIDITY_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS), key=len, reverse=True), and merge both clauses on the WHO 4 release-notes bullet.

2. [Medium — Real-Trace Fidelity] Eliminate cross-plant F461 → MH202 frame mixing, qualify unverified upward-push claims, and replay through climate.async_setup_entry

  • Where: tests/test_climate_humidity_poll_gate.py:456-512, custom_components/myhome/climate.py:529-531, docs/configuration/climate.md:67.
  • What happens:
    1. Cross-plant trace mixing & unverified upward translation: test_captured_pushes_keep_a_refusing_gateway_s_zone_fresh_without_a_poll (lines 470-488) feeds spontaneous *#4*2*60*38## pushes captured on a physical F461 (f461_live_2026-10-23 run6) into a mock MH202 handler, while climate.py:531 and docs/configuration/climate.md:67 state unconditionally that on F450, F453, F453AV, F454, H4684, MH200, MH200N and MH202 "the zone's own pushes keep it fresh there" / "their zones are not queried and show the humidity their probe pushes". Neither any physical capture in tests/fixtures/traces/ nor the firmware oracle verifies that those 8 older gateways (MH200 from 2006, H4684, F453, F454, MH202, etc.) translate SCS humidity telegrams upward into *#4*Z*60*HH## on their event session at all (per Encyclopedia ownkb:claim:c047609 / c047969, Living Now KW4691 added humidity in 2020 for F459 and 2026 for F460/F461; the only other physical gateway traced emitting *#4*42*60*52## is F455 in tests/fixtures/traces/issue_674/myhome_trace_f455_climate_timeouts_2026-10-09.json, which is outside HUMIDITY_REQUEST_REFUSED).
    2. Direct MyHOMEClimate(..., humidity=True) construction instead of dispatcher replay: Unlike #792 (4a41da61), which replays plant captures end-to-end through climate.async_setup_entry + async_dispatcher_send(hass, f"myhome_message_{mac}", ...), lines 456-512 pre-construct MyHOMEClimate with humidity=True and call zone.handle_event(...) directly, bypassing dynamic discovery and _enable_humidity(), and do not cover myhomeserver1_live_2026-10-24 run2's captured *#4*35*60## NACK on unprobed zone 35 alongside run3's *#4*39*60*51## reply (or issue_674's authentic F455 *#4*42*60*52## push).
  • Recommendation:
    • Replay f461_live_2026-10-23 (run6 + run2), myhomeserver1_live_2026-10-24 (run2 zone 35 NACK / *#4*0*60## burst + run3 zone 39 reply), and issue_674 (F455 *#4*42*60*52## spontaneous push) on their matching gateway models through climate.async_setup_entry + myhome_message_<mac>.
    • Keep any test of an MH202 entity receiving OWNHeatingEvent("*#4*2*60*38##") clearly framed as an integration-side unit test (e.g., shared-bus event delivery / YAML humidity: true) rather than claiming an F461 capture represents MH202 bus traffic, and reword climate.py:531 and docs/configuration/climate.md:67 so they state that any *#4*<WHERE>*60*<HH>## push delivered on the event session still updates humidity, without asserting that those 8 gateways translate humidity pushes upward.

3. [Low — Oracle TSV Publication & _model_key Tokenization]

  1. Publish thermo-zone-requests.tsv to own-firmware-oracle main: Commit d3767f49 on oracle/update-suites contains the CI-generated results/<P>/<V>/oracle/full/thermo-zone-requests.tsv rows and regenerated results/mcp_index.json; merging that suites update to own-firmware-oracle main ensures the machine-readable TSVs and MCP oracle index back HUMIDITY_REQUEST_REFUSED on main alongside findings/thermo-zone-requests.md.
  2. Tokenize _model_key on alphanumeric words (custom_components/myhome/gateway.py:405): Using re.findall(r"[A-Za-z0-9]+", str(name)) instead of str(name).split() ensures hyphenated or parenthesized model strings (e.g. "BTicino-L4684", "F454 (003598)") also resolve via _resolved_model to "H4684" / "F454".

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

Inline diff comments for the changes requested in #pullrequestreview-5644470090 (rebase onto v2-phase1-architecture after #792, strict real-trace fidelity without cross-gateway frame mixing, and own-firmware-oracle main TSV publication):

# F460, F461 and MyHomeServer1 forwarded it, and the live F461 / MyHomeServer1 answer it.
# Read from the OWNd profile first (``supports_heating_humidity_request``: ``True`` / ``False``
# wins, ``None`` = unverified); this table is the fallback until the flag exists there.
HUMIDITY_REQUEST_REFUSED = frozenset({"F450", "F453", "F453AV", "F454", "H4684", "MH200", "MH200N", "MH202"})

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.

  1. Merge conflict with #792 (2ece4744): Rebase onto v2-phase1-architecture and union _ZONE_REQUEST_MODELS across KNOWN_GATEWAY_MODELS | ACTUATOR_REQUEST_REFUSED | HUMIDITY_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS) while keeping one shared _model_key and _who4_target().
  2. Oracle TSV publication: There are zero physical traces on these 8 models, and the machine-readable thermo-zone-requests.tsv rows + results/mcp_index.json (OpenWebNet-HA/own-firmware-oracle@d3767f49) are still on oracle/update-suites rather than main. Please get the suites update merged to own-firmware-oracle main so HUMIDITY_REQUEST_REFUSED is backed by published oracle TSVs rather than prose-only findings.

Comment thread custom_components/myhome/gateway.py Outdated
name = getattr(profile, "model_name", None) or self.model
else:
name = self.model
for word in str(name).split():

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.

Use re.findall(r"[A-Za-z0-9]+", str(name)) instead of str(name).split() so hyphenated or parenthesized model strings (e.g. "BTicino-L4684", "F454 (003598)") also resolve through _resolved_model to "H4684" / "F454".

Comment thread custom_components/myhome/climate.py Outdated
return
# Humidity is asked only where the gateway forwards the request: the emulated firmware
# of F450, F453, F453AV, F454, H4684, MH200, MH200N and MH202 did not (own-firmware-oracle
# thermo-zone-requests finding, emulated firmware); the zone's own pushes keep it fresh there.

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.

Please drop the unverified assertion that "the zone's own pushes keep it fresh there": neither any physical trace in tests/fixtures/traces/ nor the firmware oracle verifies that these 8 older gateways (MH200, H4684, F453, F454, MH202, etc.) translate SCS humidity frames upward into *#4*Z*60*HH## on the event session at all. State only that any *#4*<WHERE>*60*<HH>## push received on the event session still updates current_humidity.

Comment thread docs/configuration/climate.md Outdated
* **Dynamic Auto-Detection**: When a climate zone emits a humidity reading on the SCS bus, the integration dynamically enables humidity tracking on that entity.
* **State Restoration Across Restarts**: The zone remembers that it supports humidity across Home Assistant restarts and restores its last known humidity value immediately.
* **Targeted Startup Query**: After startup (once the initial discovery sweep finishes), only climate zones that support humidity are queried with `*#4*<WHERE>*60##` to refresh the current reading. This avoids unnecessary bus traffic on zones without humidity sensors, and works across both software gateways (MyHomeServer1) and physical SCS gateways (such as F461, F454, F455, MH200N).
* **Targeted Startup Query**: After startup (once the initial discovery sweep finishes), only climate zones that support humidity are queried with `*#4*<WHERE>*60##` to refresh the current reading. This avoids unnecessary bus traffic on zones without humidity sensors. A live F461 and a live MyHomeServer1 answer the request (F459 and F460 forward it in the firmware emulation); on F450, F453, F453AV, F454, H4684, MH200, MH200N and MH202 the emulated firmware did not forward it (own-firmware-oracle `findings/thermo-zone-requests.md`, a `firmware_observed` verdict), so until the OWNd gateway profile states `supports_heating_humidity_request` their zones are not queried and show the humidity their probe pushes.

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.

Same here: avoid stating that F450, F453, F453AV, F454, H4684, MH200, MH200N and MH202 "show the humidity their probe pushes" — upward SCS-to-OpenWebNet translation of dimension 60 has never been traced on those 8 models (only F461, MyHomeServer1, and F455 in #674 have physical dimension-60 traces). Qualify this to say that any *#4*<WHERE>*60*<HH>## event delivered by the gateway updates humidity without polling.

Comment thread .github/release-notes/2.0.0b16.md Outdated
- **Covers**: `class:` in `myhome.yaml` now reaches WHO 2 covers (a `curtain` is no longer shown as a shutter; #39); Venetian blind slat tilt as timed slat pulses (`slat_tilt`, `slat_time`) (#508); impulse garage and gate covers on WHO 1 relays with optional safety approval, door-entry strike locks and doorbell events (#667); native broadcast for general covers behind an interface (#679); a cover stop overtakes other queued frames (#694); cover kinematics split into pluggable strategies (#739).
- **Lighting**: 2-digit Area 0 point addresses `01`–`09` (#713); ON dimmers on a fresh install are probed for brightness support (#742); timed turn-on uses the OWNd builders (#659); lights the startup sweep already reported are not polled again (#701); area and group re-syncs only skip once every member has echoed, counting only the members' own echoes in the commanded state (#740, #749).
- **Thermoregulation (WHO 4)**: season frames, a generic setpoint mode and the central unit's season (#657, #725); holiday and vacation program attributes (#689); zone humidity remembered and queried at startup (#712); F461 zones polled, AUTO on standalone zones, fan coils per season, season kept across restarts (#714, #715); `hvac_action` derived from the season when the actuator carries no heating flag (#721); standalone default follows the plant (#698); 10 s command timeouts on the F455 removed (#675); migrated to the `native_*` temperature API (#665).
- **Thermoregulation (WHO 4)**: zone humidity is not polled on F450, F453, F453AV, F454, H4684, MH200, MH200N and MH202, where the emulated firmware did not forward `*#4*<WHERE>*60##` (own-firmware-oracle `findings/thermo-zone-requests.md`, emulated firmware; an OWNd profile flag `supports_heating_humidity_request` overrides the table); season frames, a generic setpoint mode and the central unit's season (#657, #725); holiday and vacation program attributes (#689); zone humidity remembered and queried at startup (#712); F461 zones polled, AUTO on standalone zones, fan coils per season, season kept across restarts (#714, #715); `hvac_action` derived from the season when the actuator carries no heating flag (#721); standalone default follows the plant (#698); 10 s command timeouts on the F455 removed (#675); migrated to the `native_*` temperature API (#665).

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.

Merge conflict with #792 (2ece4744) on the Thermoregulation (WHO 4) bullet — please rebase and combine both clauses cleanly.

assert zone.current_humidity == 38.0


async def test_captured_pushes_keep_a_refusing_gateway_s_zone_fresh_without_a_poll(hass, f461_rows):

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.

Do not mix a live F461 capture (f461_live_2026-10-23 run6, *#4*2*60*38##) into a mock MH202 gateway under the captured-traces section as evidence that an MH202 plant pushes *#4*Z*60*HH## — we have zero physical traces of dimension 60 on MH202.

Please keep trace-backed tests strictly faithful to the gateway family each trace was captured on, replayed end-to-end through climate.async_setup_entry + async_dispatcher_send(hass, f"myhome_message_{mac}", ...) (as in #792 4a41da61) rather than pre-constructing MyHOMEClimate(..., humidity=True) directly:

  1. F461 (f461_live_2026-10-23 run6 spontaneous *#4*2*60*38## pushes enabling _humidity, and run2 *#4*2*60## → *#4*2*60*39##),
  2. MyHomeServer1 (myhomeserver1_live_2026-10-24 run2 zone 35 *#4*35*60## NACK + *#4*0*60## 17-zone burst, and run3 zone 39 *#4*39*60## → *#4*39*60*51##),
  3. F455 (tests/fixtures/traces/issue_674/myhome_trace_f455_climate_timeouts_2026-10-09.json spontaneous *#4*42*60*52## push on an unlisted SCS gateway).

If you keep an integration unit test verifying that MH202 skips *#4*2*60## even after _enable_humidity(), keep it in the unit-test section above (e.g. shared-bus delivery / humidity: true config) rather than presenting an F461 trace as MH202 traffic.

gdluck and others added 5 commits October 25, 2026 20:19
…firmware did not forward it

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bstring lookup (align with OpenWebNet-HA#792)

_model_key compared the profile's model_name with "Generic" instead of its
class: get_gateway_profile("Legrand MH200N") is a GenericGatewayProfile named
"Legrand MH200N", so the key became "LEGRANDMH200N" and missed
HUMIDITY_REQUEST_REFUSED, and the zone was polled on a gateway whose emulated
firmware does not forward *michnovka#4*Z*60##. The key is now taken from a known
profile's name, otherwise from the configured model, in which a known model is
looked up as a substring (longest first, so MH200N is not read as MH200), like
command_session_limit and supports_zone_status do.

Same helper and the same change as OpenWebNet-HA#792 (review 5639409551, finding 5), so the
two branches no longer differ in what _model_key returns.

Fail-before / pass-after on tests/test_climate_humidity_poll_gate.py: 3 of the
7 new cases fail on the previous helper (Legrand MH200N, BTicino F453AV, the
vendor-prefixed MH200N zone poll), 36 of 36 pass with the change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…umidity poll gate

own-firmware-oracle PR OpenWebNet-HA#60 merged (main 52d354b, 2026-10-25): the table's
source is now findings/thermo-zone-requests.md on main, a firmware_observed
verdict on emulated firmware, no longer an unpublished run of an open PR.
Comments, docs, release note and the test docstring say so; no behaviour
change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…arm-standby failover

`send_status_request` routes a WHO 4 request through the secondary gateway WHO 4 is
delegated to, or through the warm standby while the primary is disconnected, but
`humidity_request_refused` judged the primary's own model: a primary MH202 / F454
delegating WHO 4 to an F461 / MyHomeServer1 skipped `*michnovka#4*Z*60##` although the
request would have gone through the forwarding gateway, and a primary F461 failed
over to a standby MH202 / F454 sent it into the refusing standby (review
5644219403, finding 1).

The property now asks the same `_who4_target()` helper OpenWebNet-HA#792 adds (copied verbatim:
`_delegated_target(probe) or _failover_target(probe) or self` on a fixed WHO 4
probe) and returns that gateway's verdict when it is not this one. Thirteen test
cases: delegation to a forwarding / refusing secondary, failover to a refusing /
forwarding standby (both directions, F461 / MyHomeServer1 against MH202 / F454),
the secondary preferred to the standby, the standalone case unchanged, and two
cases through the real topology lookup (a configured secondary with WHO 4
delegated and a configured standby, connected or not). 13 fail before, 49 pass
after.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… models included

`_ZONE_REQUEST_MODELS` was built from `HUMIDITY_REQUEST_REFUSED` alone, so a
forwarding model behind a brand prefix was not reduced to its model: `_model_key`
of "BTicino F461" was "BTICINOF461", against the property's own docstring and
against OpenWebNet-HA#792, whose lookup (`ACTUATOR_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS)`)
knows F461 and MyHomeServer1 but not F450 and F454 (review 5644219403, finding 2).

`KNOWN_GATEWAY_MODELS` now lists every model the tables can be keyed on: the
twelve images the oracle emulates (refusing and forwarding) and the other models
OWNd profiles (F452, F452V, F455, H4890, MH201), and `_ZONE_REQUEST_MODELS` is
that set united with the refusing table, longest first as before. The test at the
`BTicino F461` case asserts `_model_key == "F461"`, and every known model is
checked behind a brand prefix (9 of the 17 fail on the previous lookup, the
forwarding ones). 67 pass after.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gdluck and others added 6 commits October 25, 2026 20:20
…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>
…y poll is refused

The early return of `async_update` tested `not self._humidity`, so a humidity zone
on an MH202 / F454 (no zone status request, no fan) still ran
`_poll_health.should_skip`, and could log and raise the unresponsive-zone repair,
for a poll that was never going to be sent (review 5644219403, finding 4).

`wants_humidity = self._humidity and humidity_request_refused is not True` is
computed once and used both in the early return and in the send branch; the DEBUG
line for a refused request stays. `_poll_on_add` is untouched (`_enable_humidity`
still sets it, so the first update runs after the identity settled). Tests: the
humidity zone of a refusing SCS gateway never reaches `_poll_health` (MH202, F454,
Legrand MH200N), an F450 (refuses dimension 60, answers `*michnovka#4*Z##`) still sends and
counts its zone poll, an F461 zone goes through `_poll_health` and is held back when
it skips, and `_poll_on_add` after `_enable_humidity`. 3 fail before, 92 pass after.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…HomeServer1 traces

The PR body said the captured frames were inlined in the test module, while the
module only asserted the outgoing request strings (review 5644219403, finding 4.2).
The module now loads the two committed traces the way the other trace modules do
and replays them through `MyHOMEClimate`: `f461_live_2026-10-23` run 2
(`*michnovka#4*2*60##` ACKed with `*michnovka#4*2*60*39##` in 0.14 s, echoed on the event session) and
run 6 (three spontaneous `*michnovka#4*2*60*38##` pushes, fed to a zone on a refusing MH202
to show that the pushes enable and refresh humidity without a poll), and
`myhomeserver1_live_2026-10-24` run 3 (`*michnovka#4*39*60##` ACKed with `*michnovka#4*39*60*51##`).
Five cases, 97 pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n _model_key (OpenWebNet-HA#793 review, 2)

_model_key resolved the configured model word by word on whitespace, so a
hyphenated or parenthesised string ("BTicino-L4684") was one word OWNd does not
know, and the substring fallback left it unresolved (L4684 is an alias, not a
table key). The words are now re.findall(r"[A-Za-z0-9]+", name), so
"BTicino-L4684" -> H4684 and "F454 (003598)" / "Legrand (003598)" -> F454
through _resolved_model, as the review asked.

Fail-before / pass-after on tests/test_climate_humidity_poll_gate.py: of the five
new strings "BTicino-L4684" fails on the whitespace split (the others already
resolved through the alnum-normalised word or the substring lookup); 102 pass
with the change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
OpenWebNet-HA#793 review, 3)

The comment in async_update, docs/configuration/climate.md and the test module
docstring said the zones of F450, F453, F453AV, F454, H4684, MH200, MH200N and
MH202 "show the humidity their probe pushes" / "the zone's own pushes keep it
fresh". No physical trace shows those gateways translating a probe's humidity
into *michnovka#4*Z*60*HH## on the event session (only the F461, the MyHomeServer1 and
the F455 of OpenWebNet-HA#674 have dimension-60 traces). They now say only that any
*michnovka#4*<WHERE>*60*<HH>## event the gateway delivers still updates current_humidity
without a poll; the DEBUG line of a refused request says the same. No behaviour
change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…h the climate platform (OpenWebNet-HA#793 review, 4)

The captured-frames section fed the F461's spontaneous *michnovka#4*2*60*38## pushes
(f461_live_2026-10-23, run6) into an MH202 handler and pre-built MyHOMEClimate
with humidity=True, bypassing discovery and _enable_humidity(). That test is
gone from the trace section; the MH202 case is now a unit test in the
"nothing to send" section with an inline frame, framed as integration-side
delivery (no trace claim).

The replays go through climate.async_setup_entry and
async_dispatcher_send(hass, f"myhome_message_{mac}", ...) with the helpers of
tests/test_climate_actuator_status.py (OpenWebNet-HA#792), each capture on the model it was
recorded on, with a real handler's supports_zone_status and
humidity_request_refused on the trace gateway:

- F461 run6: the 171 event-session frames; the three *michnovka#4*2*60*38## pushes
  enable humidity on a zone configured without it (38.0), the next poll then
  asks *michnovka#4*2##, *michnovka#4*2*60##. Run2: *michnovka#4*2*60## ACKed with *michnovka#4*2*60*39## in
  0.143 s (one echo on the event session), *michnovka#4*0*60## answered by zone 2 in
  2.128 s; the echo sets 39.0.
- MyHomeServer1 run2: *michnovka#4*35*60## NACKed in 2.128 s with nothing on the event
  session; *michnovka#4*0*60## ACKed in 0.499 s, 17 zones answer; on an 18-zone plant
  the 17 take their value, zone 35 stays without humidity and its poll asks
  *michnovka#4*35## only. Run3: *michnovka#4*39*60## ACKed with *michnovka#4*39*60*51## in 0.204 s; 51.0.
- F455 (OpenWebNet-HA#674): the 67 frames on a 23-zone plant (the README's zones); the
  spontaneous *michnovka#4*42*60*52## enables humidity on zone 42 alone (52.0), the
  other 22 zones stay without, and the next poll asks *michnovka#4*42*60## and no
  *michnovka#4*42## (the F455 is not in the table and does not answer zone status).

103 pass (the five replays and the MH202 unit test replace the five previous
trace cases).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/climate-humidity-poll-gate branch from 51aee89 to b0bc3d1 Compare October 25, 2026 19:03
@gdluck

gdluck commented Oct 25, 2026

Copy link
Copy Markdown
Author

Thanks, both rounds are on the branch (head b0bc3d1, rebased on 2ece474 after #792; git merge-tree clean).

  1. Rebase and one resolver: the branch is replayed on the post-feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792 base with a single _model_key (this PR's KNOWN_GATEWAY_MODELS / _MODEL_SPELLINGS / _resolved_model superset now also serves actuator_request_refused and all_zone_request), a single _who4_target(), and _ZONE_REQUEST_MODELS = sorted(KNOWN_GATEWAY_MODELS | ACTUATOR_REQUEST_REFUSED | HUMIDITY_REQUEST_REFUSED | set(ALL_ZONE_REQUESTS), key=len, reverse=True); the WHO 4 release-note bullet carries both clauses. feat(climate): request actuator status (dimension 20) on add and after reconnect, expose actuators, derive hvac_action #792's test_climate_actuator_status.py passes unchanged on the result.
  2. Tokeniser (28dfe04): _model_key reads the alphanumeric runs of the string, so "BTicino-L4684" → H4684 and "F454 (003598)" → F454, with tests.
  3. Wording (5c9c9e1): climate.py, climate.md and the description no longer claim the eight gateways push humidity upward; they say only that any *#4*<WHERE>*60*<HH>## event the gateway delivers still updates current_humidity without polling.
  4. Trace fidelity (b0bc3d1): the test that fed F461 pushes into an MH202 handler is gone from the captured-traces section (the MH202 "delivered event enables humidity, poll stays off" case is a unit test of integration-side delivery). Each capture is replayed on its own gateway model through climate.async_setup_entry + myhome_message_<mac>: F461 run6 pushes then run2 request / reply, MyHomeServer1 run2 zone 35 NACK with the *#4*0*60## 17-zone burst and run3 zone 39 reply, and the F455 issue_674 spontaneous *#4*42*60*52## push on a gateway outside the table (polled, and the push updates it).
  5. Oracle TSVs: oracle: publish the thermo-zone-requests, who1-motion-detector and who13-clock suite results (mirror of oracle/update-suites d3767f49) own-firmware-oracle#63 mirrors the CI commit of oracle/update-suites (d3767f49) unchanged, so the thermo-zone-requests.tsv rows and results/mcp_index.json reach main as a reviewable PR.

Full suite 4761 passed / 1 skipped at 100 % statement coverage against plain OWNd master; ruff, typing ratchet, HA standards, docs sync and translations clean; test_climate_humidity_poll_gate.py 103 cases. One note from an independent second-opinion review of the merged diff, for #794's landing: once #794 adds the per-zone *#4*Z*0## temperature poll on SCS-only gateways, the early-return guard that now uses wants_humidity must let that poll through (on this base nothing but the humidity request is sent past that guard for such a zone, so there is no regression here); #794 rewrites that guard and will carry it.

GreenGrassBlueOcean pushed a commit that referenced this pull request Oct 25, 2026
… (actuator status, fan speed and humidity per zone)

Two physical captures behind #792 (actuator status at startup, hvac_action from
dimension 20) and #793 (humidity gate), asked for in review 5642758778 of #792.

f461_actuators_live_2026-10-25: a local Home Assistant at #792's head restarted
with an event-only listener on the F461 2.0.11 / KW4691 zone 2 plant. The
startup sweep is followed by *#4*#0*20##, answered by both actuators within
0.13 s (ACK after 2.13 s); climate.set_temperature 26.0 makes the valve report
itself on 2.2 s after the dimension 12 echo (*#4*2#1*#20*1##, then
*#4*2#1*20*1##), the restore to 23.5 makes it report itself off 2.1 s after the
echo; the fan coil stays 5 / off. Every request is Home Assistant's, from its
debug log, placed on the listener clock (source: home_assistant, t_estimated).
The replay through the climate entity gives hvac_action idle -> heating -> idle
and 23.5 -> 26.0 -> 23.5; the actuators attribute is asserted only where the
entity exposes it.

myhomeserver1_live_2026-10-25: status reads only on the MyHomeServer1 3.87.13
plant (zones 35-70): *#4*#0*20## ACKed in 0.50 s ahead of a 1.9 s burst of 43
distinct actuator frames (34 Z#1, 5 Z#2, the pumps 0#1..0#4, every one off);
per zone *#4*Z*19## / *#4*Z*20## ACK on 34 zones and NACK on 54 / 56,
*#4*Z*11## NACK on all 36 (the fan-speed dimension is refused on a valve zone),
*#4*Z*60## ACK on the 17 zones with a probe and NACK on 19, *#4*0*60## answers
the same 17; a 600 s listen with the house's own traffic. The replay of zones 35
and 47 through the climate entity gives idle with every actuator off; the pump
frames have no route key and reach no zone.

The 34 per-zone *#4*Z*60## reads join PHYSICAL_ONLY_TX_FRAMES (no oracle
stimulus for a per-zone humidity read). Every rx frame parses in the pinned
OWNd; the '#20' command forms parse as OWNHeatingCommand (OpenWebNet-HA/OWNd#138
decodes them, both are accepted). Synthetic MACs 00:03:50:00:10:25 /
00:03:50:00:10:26, documentation IPs; no log, no local path.

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

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

All requested changes from reviews #pullrequestreview-5644219403, #pullrequestreview-5644470090, and #pullrequestreview-5644539748 are cleanly resolved on b0bc3d12:

  1. Post-#792 Rebase & Unified Model Lookup: Rebased onto 2ece4744 and merges cleanly onto v2-phase1-architecture (5641f917, post-#815) with a single _model_key (re.findall(r"[A-Za-z0-9]+", ...) tokenization, KNOWN_GATEWAY_MODELS, _MODEL_SPELLINGS, _resolved_model), a single _who4_target(), unified _ZONE_REQUEST_MODELS, and merged WHO 4 release-notes bullet.
  2. Topology Routing & Poll-Health Guard: humidity_request_refused follows WHO 4 shared-bus delegation and warm-standby failover, and wants_humidity prevents entering _poll_health.should_skip(...) on refusing SCS gateways when zero frames are sent.
  3. Strict Real-Trace Fidelity & Qualified Wording: f461_live_2026-10-23, myhomeserver1_live_2026-10-24, and issue_674 (F455) are each replayed on their own gateway family via climate.async_setup_entry + myhome_message_<mac>, and climate.py / climate.md no longer assert untraced upward humidity pushes on the 8 refusing models.
  4. Published Oracle TSVs: OpenWebNet-HA/own-firmware-oracle#63 (1908ccfb) is merged on main, publishing all 12 thermo-zone-requests.tsv suites and regenerated results/mcp_index.json.
  5. CI & Post-Merge Verification: 17/17 GitHub CI checks green; ruff check, verify_ha_standards.py (54/54), sync_documentation.py --check, manage_translations.py check, typing_ratchet.py (mypy --strict: 0 error(s)), and pytest against both editable OWNd and pinned OWNd==2.0.0b11 pass with 100% diff coverage on the merged tree.

LGTM — merging into v2-phase1-architecture.

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 217b20e into OpenWebNet-HA:v2-phase1-architecture Oct 25, 2026
17 checks passed
gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 25, 2026
… 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>
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