Repository navigation
fix(climate): do not latch the setpoint in force on central-unit zones that report dimension 14 alone - #809
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…michnovka#4*Z## OpenWebNet-HA#791 made a zone's dimension 12 report (the setpoint in force, knob offset included) hold the displayed target against later dimension 14 and 13 frames. Central-unit plants behind an SCS gateway report dimension 12 once and then update a zone with dimension 14 and 13 alone, and async_update never polls *michnovka#4*Z## there (OpenWebNet-HA#674), so the zone stayed on the stale value: - MH200N sweep (tests/fixtures/traces/issue_466/myhome_sweep_MH200N_all_ 2026-09-25T14-40-10.json, frames 159-191, no *michnovka#4*2## sent): zone 2 reports *michnovka#4*2*12*0105*3## spontaneously, then *michnovka#4*2*14*0100*3## + *michnovka#4*2*13*00## + *4*110*#2## 9.7 s and 20.6 s later with no dimension 12; the entity stayed at 10.5 instead of 10.0. - F453AV (tests/fixtures/traces/issue_619/): the 10:29:57 sweep reports zone 14 with 12 = 8.0 then 14 = 10.0; Daily Holiday (11-02-55.Holiday.ON) emits 23 dimension 14 frames and no dimension 12; the entity stayed at 8.0. The hold is now armed only when the gateway answers the per-zone status request (F461, MyHomeServer1), where every poll and every write echo repeats dimension 12 and homeassistant.update_entity can refresh it. On SCS gateways the slider shows the commanded setpoint plus the knob offset, as before b16. A time window between dimension 12 and 14 was rejected: the F453AV sweep sends all dimension 12 frames first and all dimension 14 frames afterwards (1.1 s to 14 s apart for one zone, the 14 repeated 1.3 s later), which overlaps the 9.7 s of the MH200N update, and the F461 answers separate dimension reads 2.6 s apart. Gating on the zone's central flag changes nothing: zones of a central-unit plant are not flagged central. tests/test_climate_setpoint_latch_central_plants.py replays both captures (2 of 9 cases fail on d8bac15, both pass on the pre-OpenWebNet-HA#791 base) and pins the F461 live poll reply, write echo (run1, run3 of f461_live_2026-10-23) and the 2026-10-24 antifreeze split on a pollable gateway. Refs OpenWebNet-HA#791, OpenWebNet-HA#674, OpenWebNet-HA#383, OpenWebNet-HA#454. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
e485728 to
52d965b
Compare
|
Status after #818 merged: this PR rebases cleanly, but #818's What the traces say. The hold of #791 (dimension 12, the setpoint in force, held against later dimension 14 / 13 frames) was challenged with two central-unit plants where a lone dimension 14 must win: MH200N zone 2 emits Proposed fix (replaces the gateway gate). A per-zone report window: on dimension 12 record its arrival and arm the hold; on dimension 14 or 13, if a dimension 12 arrived within Known limits of the heuristic (to be tests, not surprises): a central unit that changes the setpoint with a 14 within 2 s of a 12 would be read as a companion; a companion delayed beyond 2 s would be read as an update; a reversed I will rework the PR along these lines if you agree with the rule; say if you would rather close it and keep #791 as merged. |
|
Agreed that gating 1. Why
|
|
Thank you for replaying the committed captures against the window; the F461 |
Requested by A
What is the problem
#791 made a zone's dimension 12 report (the setpoint in force, knob offset included) hold the displayed target temperature against later dimension 14 (commanded setpoint) and dimension 13 (knob offset) frames. The review of #791 (comment 6388207693, point 2) showed where that hold goes stale:
Both reproduce by replaying the fixture frames through
MyHOMEClimate.handle_eventond8bac157(which containsabf6330f): the MH200N zone 2 ends at 10.5 instead of 10.0, and F453AV zone 14, which the 10:29:57 sweep reports with*#4*14*12*0080*3##then*#4*14*14*0100*3##, ends at 8.0 instead of 10.0 after Daily Holiday. (The 10:49:26Central.to.OFFcapture latches zones at 22.0/23.0 while they are OFF, but the*4*111*#Z##frame of the Holiday capture takes them through OFF → heating, which already clears the hold, so those zones end at 10.0 on both revisions; the sweep → Holiday path on zone 14 is the one that stays stuck.)Refs #791 (merged), #674 (why
*#4*Z##is not polled on SCS gateways), #383 and #454 (antifreeze tests, unchanged). No open issue is closed by this change.What is the solution
climate.py: the hold is armed only where a poll can refresh it. In the dimension 12 handler,_local_target_reported = value is not None and self._gateway_supports_zone_status. On an F461 or MyHomeServer1 (the gateways that answer*#4*Z##) nothing changes: every poll reply and every write echo repeats dimension 12 before dimension 14, andhomeassistant.update_entityrefreshes it. On a gateway that is never polled per zone (F454, F455, MH200N, MH201, MH202, F452, F453, F458, F459, H4684: thesupports_zone_statustable ingateway.py) a dimension 14 or 13 frame updates the slider as before b16 (commanded setpoint plus knob offset).f461_live_2026-10-23, run 1). A 2 s window passed the MH200N replay but turned the F453AV sweep into "12 for 2 s, then 14" and would flip an F461 zone to the commanded value after a*#4*Z*14##read by hand.not self._central: zones of a central-unit plant are not flagged central in discovery (only#0/#0#1are), so both replays still fail with it.gateway.has_central_unit): the MH200N capture has no#0entity (its zones answer as sub-centrals,*4*110*#2##), so the MH200N replay still fails.docs/configuration/known_limitations.md,docs/configuration/climate.mdand the b16 release note: a thermostat behind an SCS gateway that regulates to another setpoint than the commanded one (no such capture exists; the 12 ≠ 14 split was seen on the F461 only) is shown with the commanded value plus the offset there.Companion PR: none.
State Diagram
stateDiagram-v2 direction TB state "Gateway answers *#4*Z## (F461, MyHomeServer1)" as Pollable { [*] --> Nominal : *#4*2*14*T*3## , no dimension 12 known\nshow T + offset Nominal --> InForce : *#4*2*12*T*3## , zone running\nshow T as is, hold armed InForce --> InForce : *#4*2*14*..## or *#4*2*13*..##\ndisplay unchanged, commanded value stored InForce --> InForce : next poll *#4*2##\n12 first, then 14 and 13 InForce --> Nominal : OFF to on (#383) or climate.set_temperature\nhold cleared } state "Gateway cannot be polled per zone (MH200N, F453AV, F454, ...)" as Scs { [*] --> Shown : *#4*2*12*0105*3## spontaneous\nshow 10.5, no hold Shown --> Shown : *#4*2*14*0100*3## 9.7 s later , no dimension 12\nshow 10.0 + offset Shown --> Shown : *#4*Z*14*0100*3## on Daily Holiday , no dimension 12\nshow 10.0 + offset }Hardware Verification
*#4*2##). The live sequences are pinned by the new tests: run 1 poll reply*#4*2*0*0239##,*#4*2*12*0235*3##,*4*1*2##,*#4*2*14*0210*3##,*#4*2*13*00##→ 23.5 shown, 21.0 kept as the commanded value; run 3 write*#4*2*#14*0235*3##→ echo*#4*2*12*0235*3##,*4*1*2##, then the next poll with 14 = 23.5 → 23.5; 2026-10-24 antifreeze split*4*102*2##→*#4*2*12*0070*3##while 14 stays 23.5, poll reply0, 12 = 0070, 102, 14 = 0235, 13 = 00→off, 7.0; restore →*4*1*2##, 12 = 14 = 23.5 →heat, 23.5.How is it tested
tests/test_climate_setpoint_latch_central_plants.py(9 cases):test_mh200n_zone_2_follows_the_dimension_14_update_reported_without_dimension_12: every received WHO 4 frame of zone 2 (2and#2) of the MH200N sweep, in capture order; asserts no*#4*2##was sent, one*#4*2*12*0105*3##and no dimension 12 after it; 10.5 after frame 161, 10.0 at the end;test_f453av_zone_14_holiday_on_updates_the_zone_without_dimension_12: zone 14 frames of the 10:29:57 sweep (8.0 after12*0080, 10.0 after14*0100), then its Holiday frames*4*111*#14##,*4*111*#14##,*#4*14*0*0280##,*#4*14*14*0100*3##→ 10.0; asserts the Holiday capture holds 23 dimension 14 frames (22 of them0100) and no dimension 12;test_a_knob_turn_reported_by_dimension_13_alone_moves_the_slider(MH200N dial turns, frames 106–130 pattern),test_an_scs_gateway_shows_the_commanded_setpoint_plus_offset_as_before(the F461 reply order on a non-pollable gateway: 21.0, status quo);test_f461_live_poll_replies_show_the_setpoint_in_force,test_f461_live_write_echo_then_poll_keeps_the_written_setpoint(runs 1 and 3 of the 2026-10-23 archive),test_antifreeze_split_seen_live_on_2026_10_24,test_a_poll_or_report_with_dimension_12_last_shows_it_on_both_kinds_of_gateway(×2).d8bac157(MH200N 10.5 ≠ 10.0, F453AV 8.0 ≠ 10.0) and pass on the pre-fix(climate): show the setpoint in force (dimension 12), keep dimension 14 as the commanded value #791 base974306b5; 5 of the 9 cases fail ond8bac157, all 9 pass with the change.tests/test_climate_setpoint_in_force.py(12),test_issue_454_dim12_mode3.py,test_component_climate.py([BUG} Climate entities show 17 °C instead of the active 7 °C antifreeze target #383 antifreeze sweep),test_issue_429_zone_state.py,test_f461_live_2026_10_23_trace.py,test_issue_466_mh200n_trace.pyandtest_issue_619_who4_trace.pypass unchanged.master, f04d8aa, onPYTHONPATH): 4507 passed, 1 skipped, 100.0 % statement coverage (16071 statements), 3 min with-n 4.ruff check .,scripts/typing_ratchet.py(0 strict errors),scripts/verify_ha_standards.py,scripts/sync_documentation.py --check,tests/test_fixture_privacy.py: all pass. No new fixture files.Where are the traces
tests/fixtures/traces/issue_466/myhome_sweep_MH200N_all_2026-09-25T14-40-10.json, frames 159–191 (#466)*#4*2##sent*#4*2*12*0105*3##(14:39:27),*#4*2*14*0100*3##+*#4*2*13*00##+*4*110*#2##(14:39:37 and 14:39:48)tests/fixtures/traces/issue_619/myhome_trace_F453AV_who4_2026-10-11T10-29-57.jsonand...11-02-55.Holiday.ON.json(#619)#0*#4*14*12*0080*3##,*4*1*14##,*#4*14*14*0100*3##×2; Holiday:*4*115#1101*#0##, 23*#4*Z*14*…*3##, no dimension 12tests/fixtures/traces/f461_live_2026-10-23/myhome_trace_F461_live_2026-10-23.json, runsrun1_oracle_replay_readonly,run3_climate_setpoint_restore(#799)*#4*2##→0,12 = 0235,*4*1*2##,14 = 0210,13 = 00; write*#4*2*#14*0235*3##→*#4*2*12*0235*3##,*4*1*2##, then the poll with14 = 0235*#4*2##poll (frames inlined intest_antifreeze_split_seen_live_on_2026_10_24; not a fixture file)*4*102*2##→*#4*2*12*0070*3##; poll0, 12 = 0070, 102, 14 = 0235, 13 = 00; restore*4*1*2##,12 = 14 = 0235What does the Firmware Oracle / Encyclopedia say
functional/who-4-temperature-control/dimensions.md):DIMENSION 12is the complete probe status with the setpoint after the local offset;DIMENSION 14the setpoint,DIMENSION 13the local offset. The encyclopedia describes the frames, not which gateway repeats dimension 12 on a change: that comes from the captures above (F461 and MyHomeServer1 do on every poll and write; MH200N and F453AV update with dimension 14 alone).*#4*Z##is the request the gate is keyed on. The publishedresults/<P>/<V>/oracle/full/trace-corpus.tsvrows show*#4*1*12##and*#4*1*14##forwarded to the bus on all twelve images (no thermostat behind the emulated gateways, so no 12 ≠ 14 pair is produced); the per-zone status NACK/timeout of the SCS gateways thatsupports_zone_statusencodes is the F455 multiple timeouts #674 finding (F455, 10 s command-session timeout per zone), not an oracle verdict.Checklist
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).handle_event()and do not manually subscribe viaasync_dispatcher_connect.pytest,ruff, andscripts/verify_ha_standards.pypass.🤖 Generated with Claude Code