Repository navigation
fix(climate): show the setpoint in force (dimension 12), keep dimension 14 as the commanded value - #791
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…penWebNet-HA#136-OpenWebNet-HA#142 and MyHOME OpenWebNet-HA#791-OpenWebNet-HA#798 The maintainer asked on OpenWebNet-HA/OWNd#137 (comment 6384484511) for the physical trace behind the 2026-10-24 pull requests to be added to this repo like every capture before. Same plant and format as f461_live_2026-10-15 and f461_live_2026-10-18: a BTicino F461 2.0.11 (WHO 13 device type 200, WHO 1013 134.15.5.0) with BT-F411/4 lights at 06 / 07, an F422 light at 09 whose actuator lacked configurator 2 at capture time, a BT-F411/2 5 s shutter at 05 and a KW4691 standalone thermostat at zone 2 with a BT-F430/2 valve and a BT-F430/4 fan coil; no central unit. tests/fixtures/traces/f461_live_2026-10-23/: the derived trace archive (299 event-session frames, 939 tx / rx / result rows in ten runs: the oracle replay of 165 read-only requests, reviewer reads, setpoint restore, final state, report-form writes, the event session during a local Home Assistant run, the bus monitor of an earlier run, two flicker re-tries) and its README with the plant, the runs, what the plant established and which pull request each frame backs. MAC / IP are synthetic in both their text and WHO 13 byte forms. tests/test_f461_live_2026_10_23_trace.py pins the facts the pull requests rely on: the *michnovka#4*2## reply order with dimension 12 before and different from 14, *michnovka#4*2*20## followed by two actuator frames and *michnovka#4*#0*20## answering both, *#1*0007## NACK vs *#1*07## ACK vs *#1*7## empty, *OpenWebNet-HA#13**0## echoed on the event session, every WHO 16 request refused in under 10 ms, the dimension 27 model name frame, *michnovka#4*#2*14## and the other central-unit forms refused, one *2*0*0##, the shutter's own stop 5.1-5.9 s after each run; plus entity replays through handle_event that pass on the base branch with the pinned OWNd. tests/test_trace_oracle_validation.py: tier 4 tolerates the authentic *OpenWebNet-HA#13**27*70#52#54#49## reply until the pinned OWNd carries the dimension 27 decoder (OWNd#139); it parses to None without raising. The *4*311*2## NACK / *4*311*#2## ACK pair cited by OWNd#137 is already in f461_live_2026-10-15 and is referenced, not duplicated. scripts/update_trace_matrix.py leaves the matrix unchanged (the F461 row already covers WHO 1, 2, 4, 13, 1013). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
9ac02da to
d268c0c
Compare
…on 14 as the commanded value The dimension 14 handler recomputed the displayed target from the commanded setpoint plus the knob offset, overwriting a dimension 12 report received in the same poll. On a live F461 2.0.11 the reply to *michnovka#4*2## is *michnovka#4*2*0*0239##, *michnovka#4*2*12*0235*3##, *4*1*2##, *michnovka#4*2*14*0210*3##, *michnovka#4*2*13*00##: Home Assistant showed 21.0 while the thermostat regulated to 23.5. Dimension 12 follows every write (*michnovka#4*2*OpenWebNet-HA#14*0220*3## is answered with *michnovka#4*2*12*0220*3##). A dimension 12 report now stays what is shown (flag _local_target_reported); dimension 14 is kept as the commanded value and dimension 13 no longer adds the offset on top of a dimension 12 report. _show_nominal() restores the nominal plus offset when the zone leaves OFF or a frame carries a setpoint of its own, so a protection value seen while OFF is still dropped (OpenWebNet-HA#383, OpenWebNet-HA#454). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Code review / audit for #791
Summary
The direction is correct and the protocol grounding is solid: dimension 12 is the setpoint after local offset and should be what the entity shows when present; dimension 14 stays the commanded value. The _local_target_reported hold, the clear-on-write, and the _show_nominal() path for leaving OFF look like the right shape, and the new tests exercise the captured F461 frame order, both arrival orders, offset double-counting, protection transitions (#383), and the write-echo path.
The verification for the motivating case (dim12 ≠ dim14) is only partial, which is the main blocker for me.
Verification gap
The unequal case that triggered the change was observed on 2026-10-23 on the unpatched branch (*#4*2*12*0235*3## then *#4*2*14*0210*3##). The 2026-10-24 live retest on this branch is labelled partial in the PR itself: the write path and the equal 12=14 case were confirmed, but the dim12 ≠ dim14 display case was not reproduced live with the patched code. It rests entirely on the unit test replaying the old capture.
The Firmware Oracle produces no dim12 ≠ dim14 stimulus, and the MyHomeServer1 capture only confirms reply order (values were equal). For a climate setpoint change that touches protection-window logic and display hold, that is thinner than the bar we have been applying.
Specific points
-
Live confirmation of the unequal case
Please reproduce dim12 ≠ dim14 on the F461 (or another plant) while running this branch and confirm the entity shows the dimension-12 value. A short bus-monitor + entity-state snippet is enough. Without that, the core behavioural claim is only unit-test-replayed. -
Known limit
The stated limit (“once dim12 is known, a dim14 that is not the echo of our own write does not update the display until the next dim12”) is acceptable if both captured plants always emit dim12 after a write, but it should be called out more visibly in the docs / release note (not only in the PR body). If we ever see a gateway that echoes only dim14 for an external change, the display will be stale until the next poll. -
Write-clear interaction
Clearing_local_target_reportedinsideasync_set_temperatureis the right fix so a dim14-only echo is not ignored. The new test covers it; just make sure future changes that touch the hold flag also cover the “write then dim14-only echo” path. -
Housekeeping
Draft + dirty mergeable state. Needs a rebase and to be marked ready once the live confirmation is attached.
Recommendation
Request changes until the unequal dim12/dim14 case is shown live on this branch (or equivalent additional independent evidence is added). The implementation and unit tests look good; the missing piece is the live proof of the exact condition the change is meant to fix.
d268c0c to
4566943
Compare
…itations and the release note Review follow-up on OpenWebNet-HA#791: the limit (a commanded setpoint that arrives without a following dimension 12 does not move the slider once the zone has reported a setpoint in force) was only in the PR body. It is now a row in the WHO 4 section of docs/configuration/known_limitations.md and a sentence in the b16 release note, with the two captured plants that always emit dimension 12 named. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the audit. Points 2 and 3 are done; point 1 was attempted live today and I want to be exact about what it did and did not show. Point 1 (live dim 12 ≠ dim 14 on this branch). Local Home Assistant on this branch head, pinned OWNd 2.0.0b11, F461 2.0.11, KW4691 zone 2, second event-session listener recording. Baseline:
So I could not produce the unequal state today on either plant. The only physical record of it remains the 2026-10-23 capture in the merged Point 2. Commit abf6330 adds the limit as a row in the WHO 4 section of Point 3. Agreed; the "write then dimension-14-only echo" path is pinned by Point 4. Rebased on the current base (mergeable). It stays draft for now because the un-drafting is being done in the phases announced on #795; say the word and I flip it. |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Audit notes
Logic is correct and fail-soft. The diagnostic path cannot raise into the outer generic ERROR.
MAC normalization is consistent with the existing matching loop that already uses dr.format_mac.
Disabled-entry behavior is intentional and now documented; it differs from the top-level-platforms path but is reasonable (the entry still exists and owns its section).
Logging is precise, actionable, and does not spam multi-gateway setups.
No protocol, entity, or runtime behavior changes outside the missing-MAC diagnostic.
Style, typing, and HA standards checks pass.
Minor: the broad except Exception is appropriate for a diagnostic-only helper.
27dd8e3
into
OpenWebNet-HA:v2-phase1-architecture
|
Thanks for running the live experiments on both plants and reporting the exact results transparently — keeping this in draft until we have a causal 1. How to trigger
|
|
Follow-up on point 1: the unequal case is now reproduced live on this branch, with the help of a second opinion that pointed at the protection modes as the gateway-triggerable way to split the two dimensions. Setup unchanged: local Home Assistant on head abf6330, pinned OWNd 2.0.0b11, F461 2.0.11, KW4691 zone 2, second event-session listener. Baseline Trigger (18:58 UTC): Home Assistant's own poll of the zone (18:58:46, Entity after that reply: Restore (18:59): Raw rows ( |
|
Here is the one-session Before. Trigger attempts without a dimension-14 write.
After (split). Poll at 19:10:54: Re-convergence, both directions.
So on this thermostat the split is produced by the protection modes (and, per your explanation, by the schedule/preset paths on units whose preset value differs and that do not realign dimension 14); every local or remote setpoint change realigns dimension 14 to dimension 12 within a minute. I am preparing the whole session as a trace fixture PR ( |
|
Section 2 (central-unit plants latching on a stale dimension 12) is addressed in draft #809 with regression tests built from the two fixtures you named. Summary, details in the PR body:
A per-zone "report epoch" rule (a mode/program transition invalidates the hold) is the better long-term shape, but it needs the transition captures on both plant types; the F461 + KW4691 session above is the first half of that evidence. |
|
The session above is now a trace-fixture draft, #810 ( |
|
Follow-up capture with the DIMENSION 7 frames you named, from the 23.5 baseline (20:08–20:11 UTC, same setup, the same Home Assistant instance as the earlier runs, still on the branch head abf6330 that was merged, event listener recording). This one produces the persistent split on the F461 + KW4691 exactly as your explanation predicts:
So a preset changes dimension 12 and leaves dimension 14 at the last manual value for as long as the preset is active (≥ 48 s here, no realignment), while a manual write realigns both. Home Assistant received the probe's dimension 12 and 14 reads on its event session ( The zone echoes a mode frame for each preset that OWNd does not decode yet: [Edit: two corrections while turning this into the trace fixture (#812): Home Assistant issued no |
…ure (knob, antifreeze split, presets, bezel, HA write) The before / trigger / after capture asked for on #791 (comment 6388207693): what moves WHO 4 dimension 12 (the setpoint in force) and dimension 14 (the last commanded value) on the Living Now KW4691 standalone zone 2 behind the physical F461 2.0.11, as a derived_trace_archive with a README and a replay test module. Five runs: a bezel turn, 480 alternating dimension 12 / 14 reads 0.6 s apart across two Home Assistant writes, the antifreeze command *4*102*2## and the restore write, the dimension 7 templates *#4*2*#7*1*3## / *#4*2*#7*2*3##, and bezel presses in Manual and in antifreeze followed by a Home Assistant write while 12 != 14. What the plant said: a bezel turn emits dimension 12 (and the season frame) only, the first dimension 14 read 49 s later already carries the value; a dimension 14 write echoes dimension 12, never dimension 14, and both read the new value within a poll cycle; antifreeze is the only operation that left 12 (7.0, with the mode frame *4*102*2##) apart from 14; a bezel press in antifreeze leaves it at the protection value plus one step on dimension 12 with 14 following; a write realigns both. The dimension 7 templates are echoed as heating / cooling comfort with the mode frames *4*108*2## / *4*208*2##; the zone then flipped season twice and settled at 22.5 on both dimensions without a write. Two event-only listeners covered the whole capture; each run takes its frames from the listener and the probe's command rows are aligned to the listener clock through the frames both sessions received (the probe's own event log matched the listener exactly over every request window). Home Assistant's own requests (its polls and writes) are listed in capture.home_assistant_requests from its debug log, which is not included. Synthetic MAC / IP as in f461_live_2026-10-23; no WHO 13 identity frame was exchanged. tests/test_trace_oracle_validation.py: the two dimension 7 template requests join PHYSICAL_ONLY_TX_FRAMES (no oracle stimulus for them). Refs #791 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…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>
…ure (dimension 12 split from 14) (#812) * test(trace): ingest the F461 2.0.11 heating comfort / eco preset capture (dimension 12 split from 14) Follow-up asked for in the review of #810: from a 23.5 baseline on the physical F461 2.0.11 + Living Now KW4691 (zone 2, heating season), the dimension 7 templates *#4*2*#7*1*3## (heating comfort) and *#4*2*#7*1*4## (heating eco), each followed at once by reads of dimension 12 and 14, a *#4*2## poll and reads 48 s into eco, then Home Assistant's climate.set_temperature 23.5 while the two dimensions differed (2026-10-24 20:08-20:12 UTC; results posted on #791). What the plant said: a heating preset moves dimension 12 (21.0, then 18.0) and leaves dimension 14 at the last commanded value (23.5) for as long as it was observed (48 s); the mode frame *4*108*2## / *4*107*2## takes the season frame's place in the dimension 12 and *#4*2## replies; the write is echoed as *4*1*2##, *#4*2*12*0235*3##, *4*1*2##, *#4*2*0*0238## and the poll 7.4 s later reads 12 = 14 = 23.5. On the base branch the climate entity shows 23.5 -> 21.0 -> 18.0 in heat and 23.5 after the write. Archive tests/fixtures/traces/f461_presets_live_2026-10-24/ in the format of f461_live_2026-10-24 (47 event-session frames, 122 rows, two runs, one listener, probe rows aligned to the listener clock, t_estimated on the command-session replies, events_after bounded by the ACK, synthetic MAC / IP), its README, tests/test_f461_presets_live_2026_10_24_trace.py (9 tests) and *#4*2*#7*1*4## in PHYSICAL_ONLY_TX_FRAMES (no oracle stimulus; tier 1 failed on it before). *4*107*2## / *4*108*2## parse with mode = None (OpenWebNet-HA/OWNd#146). Refs #791 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(trace): accept the OWNd#146 decoding of WHAT 107 / 108 / 208 beside the pinned None Review of #812 (5642383839): OWNd's required MyHOME conformance job runs this suite against every OWNd PR, so the two trace tests that pinned `mode is None` for *4*107*2##, *4*108*2## and *4*208*2## would fail the moment OpenWebNet-HA/OWNd#146 decodes them (heating eco / comfort, cooling comfort). Both locations (this PR's presets test and the merged f461_live_2026-10-24 test) now accept None or the decoded mode / season, with the "mode is unknown" log tied to the None case, as tests/test_message_edges.py does for the two OWNd generations. Rounding nit: the *#4*2## poll 15.53 s into eco is written +15.5 s everywhere (test docstring, archive purpose), as in the README. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: gdluck <innowebdevs@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
… the nominal (#791 follow-up) The saved `temperature` is what the slider showed, which always carries the knob offset: the setpoint in force (dimension 12, offset included) once the zone has reported it, else the commanded setpoint plus the offset. The restore path put that value into _target_temperature, the nominal slot, with the offset still 0, so the first dimension 13 after a restart added the offset a second time: 23.5 restored with offset +2, `*#4*2*13*02##` -> 25.5 shown until the next dimension 12 report (the live path showed 23.5). The offset and the local target were saved as attributes but never read back. The restore now follows the live rule: the saved `local_offset` is restored, the nominal is the shown value less that offset and the shown value goes into the local-target slot. The flag stays False, so a dimension 14 before the next dimension 12 shows the commanded setpoint plus the offset, as a fresh entity does. A state saved without a usable offset is read as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Record note from the WHO 4 PDF audit of 2026-10-26 (no change requested): this PR writes the setpoint on the plain zone, |
Requested by A
What is the problem
A zone's displayed target temperature was overwritten by dimension 14 (the commanded setpoint) even when the thermostat had reported a different setpoint in force (dimension 12). On a live F461 2.0.11 plant the reply to
*#4*2##carried*#4*2*12*0235*3##(23.5 in force) followed by*#4*2*14*0210*3##(21.0 commanded): the dimension 14 handler recomputed_local_target_temperature = target + offset, and the dimension 13 handler added the knob offset once more on top of a dimension 12 value that already includes it. The entity showed 21.0 while the thermostat regulated to 23.5. On this gateway dimension 12 follows every write (*#4*2*#14*0235*3##is ACKed in 78 ms and answered with*#4*2*12*0235*3##), so it is the value in force.Refs #383 and #454 (dimension 12 while OFF is the protection setpoint; both test sets pass unchanged). No open issue is closed by this change.
What is the solution
climate.py: a dimension 12 report holds the display (_local_target_reported):_target_temperature, but no longer overwrites the displayed value while a dimension 12 report is known;_show_nominal()(nominal + offset, clears the hold) replaces the recompute in four places: dimension 7 setpoint, season OFF→on, mode OFF→on and mode-with-setpoint. Leaving OFF therefore still drops a protection value seen while OFF ([BUG} Climate entities show 17 °C instead of the active 7 °C antifreeze target #383). The live reply order puts dimension 12 before the season frame, so when a zone is switched on at the thermostat the value reported while Home Assistant still saw it OFF is taken as protection and the commanded setpoint is shown until the running zone reports dimension 12 again (its next poll); a test pins that order.async_set_temperatureclears the hold after writing*#4*Z*#14*T*3##: the write's echo decides what is shown next, dimension 12 where the gateway reports it (F461, MyHomeServer1), otherwise a dimension-14-only echo. Without this a gateway that echoes only dimension 14 after a write would keep showing the previous value in force.docs/configuration/climate.md) and the b16 release-notes delta.Companion PR: None required. Related: OpenWebNet-HA/OWNd#138 (draft) parses
#-prefixed dimension 14 values; this PR does not depend on it.State Diagram
stateDiagram-v2 direction TB state "1. Before: the last frame wins" as Before { [*] --> InForce : *#4*2*12*0235*3##\nshow 23.5 InForce --> Commanded : *#4*2*14*0210*3##\nshow 21.0 + offset Commanded --> Commanded : *#4*2*13*OO##\nadd the offset again } state "2. After: dimension 12 holds until the zone reports again" as After { [*] --> Nominal : *#4*2*14*T*3## with no dimension 12 known\nshow T + offset, reported = False Nominal --> InForce : *#4*2*12*T*3## , zone running\nshow T as is, reported = True InForce --> InForce : *#4*2*14*..## or *#4*2*13*..##\ndisplay unchanged, commanded value stored InForce --> Nominal : OFF to on , *4*1*2## after *4*102*2##\n_show_nominal, #383 InForce --> Nominal : climate.set_temperature\n*#4*2*#14*0220*3## written, hold cleared Nominal --> InForce : echo *#4*2*12*0220*3## Nominal --> Nominal : echo *#4*2*14*0220*3## only\nshow 22.0 + offset }Hardware Verification
*#4*2##answered in 1.3 s with*#4*2*0*0239##,*#4*2*12*0235*3##,*4*1*2##,*#4*2*14*0210*3##,*#4*2*13*00##; Home Assistant showedtemperature: 21.0while 23.5 was in force.climate.set_temperature22.0 wrote*#4*2*#14*0220*3##and the bus monitor saw*#4*2*12*0220*3##then*4*1*2##2 ms later. With this change the same plant shows 23.5 after start and 22.0 after the write.*#13**16*3*87*13##, WHO 1013 model67; the oracle emulates 2.82.06), 2026-10-24, status reads only:*#4*35##→*#4*35*0*0231##,*#4*35*12*0200*3##,*4*1*35##,*#4*35*14*0200*3##,*#4*35*13*00##. Same reply order as the F461 (dimension 12 before dimension 14), so the handler change applies to this gateway family too; dimension 12 and 14 were equal there (20.0), so this plant confirms the ordering only, not a differing setpoint.Live retest (2026-10-24)
Consensus label: partial. On the F461 with this branch (combined build):
climate.set_temperature 22→tx *#4*2*#14*0220*3##→rx *#4*2*12*0220*3##,*4*1*2##,*#4*2*0*0241##; the poll*#4*2##→*#4*2*0*0241##,*#4*2*12*0220*3##,*4*1*2##,*#4*2*14*0220*3##,*#4*2*13*00##; the entity showedtemperature: 22.0andlocal_target_temperature: 22.0within 0.14 s of the dimension 12 reply, and 23.5 at startup from*#4*2*12*0235*3##. The command, the ACK and the dimension-12-driven state are confirmed; the dimension 12 ≠ dimension 14 display case was not reproduced live on this build (one write sets both on this plant; it was seen on 2026-10-23 only) and rests on the unit test replaying that capture.How is it tested
tests/test_climate_setpoint_in_force.py(12 cases):test_live_f461_poll_reply_shows_the_setpoint_in_force: the captured reply order shows 23.5 and keeps 21.0 as the commanded value;test_dimension_14_before_dimension_12_shows_the_setpoint_in_force,test_running_zone_keeps_dimension_12_in_either_order(×2),test_second_poll_does_not_flip_the_target;test_offset_is_never_added_to_dimension_12: knob offset +2 with 23.5 in force stays 23.5;test_without_dimension_12_the_commanded_setpoint_plus_offset_is_shown;test_manual_write_echo_updates_the_setpoint_in_force: the F461 echo*#4*2*12*0220*3##,*4*1*2##;test_set_temperature_writes_dimension_14_and_shows_the_echo(new):async_set_temperature(22.0)sends exactly*#4*2*#14*0220*3##, the hold is cleared, a dimension-14-only echo shows 22.0, the dimension 12 echo re-arms the hold;test_dimension_12_just_before_leaving_off_is_treated_as_protection(new): OFF with 7.0, then dimension 12 (23.5) right before*4*1*2##: 21.0 is shown until the running zone reports dimension 12 again;test_protection_then_back_on_shows_the_nominal_again([BUG} Climate entities show 17 °C instead of the active 7 °C antifreeze target #383),test_nothing_is_shown_before_any_setpoint_is_known.set_temperaturecase also fails on the previous revision of this branch (the hold was not cleared by a write).test_issue_454_dim12_mode3.py,test_component_climate.py(antifreeze sweep) andtest_issue_429_zone_state.pypass unchanged.master, f04d8aa, onPYTHONPATH): 4467 passed, 1 skipped, 100.0 % statement coverage (15970 statements).ruff check .,scripts/typing_ratchet.py(0 strict errors),scripts/verify_ha_standards.py,scripts/sync_documentation.py --check: all pass.Where are the traces
Trace PR: #799 (merged,
ad8431ff).tests/fixtures/traces/f461_live_2026-10-23/myhome_trace_F461_live_2026-10-23.json, runsrun1_oracle_replay_readonlyandrun4_final_state*#4*2##→*#4*2*0*0239##,*#4*2*12*0235*3##,*4*1*2##,*#4*2*14*0210*3##,*#4*2*13*00##(run 1, 1.3 s); run 4 shows 12 = 14 = 21.0 after the Home Assistant sessionrun3_climate_setpoint_restore,run4b_final_state_after_restore*#4*2*#14*0235*3##ACK 78 ms, then*#4*2*12*0235*3##and*4*1*2##; two hours later 12 = 14 = 23.5run7_localha_bus_monitorset_temperature 22→*#4*2*12*0220*3##,*4*1*2##(the outgoing*#4*2*#14*0220*3##went on the command session and is not in the event-session archive; it is the frame the new test pins)tests/fixtures/traces/myhomeserver1_live_2026-10-24/myhome_trace_MyHomeServer1_live_2026-10-24.json, runrun2_reads_thermo_sound_alarm_lights*#4*35##→*#4*35*0*0231##,*#4*35*12*0200*3##,*4*1*35##,*#4*35*14*0200*3##,*#4*35*13*00##(ordering only: 12 = 14 = 20.0)The frames are also inlined in
tests/test_climate_setpoint_in_force.py.What does the Firmware Oracle / Encyclopedia say
functional/who-4-temperature-control/dimensions.md):DIMENSION 12"complete probe status": the response*#4*WHERE*12*T*3##gives the setpoint after local offset; the trailing3is fixed in the published flow and the heating / conditioning / protection state comes in a separate*4*WHAT*WHERE##frame. The historical touchscreen client reconstructs its base setpoint by subtracting the local offset fromT(offset03withT=0250gives0220).DIMENSION 14"setpoint temperature", read / write;DIMENSION 13"local set offset", separate from the central target.results/<P>/<V>/oracle/full/trace-corpus.tsvrows*#4*1*12##and*#4*1*14##only show that both requests are forwarded to the bus on all twelve images (nack,outon eleven;ack,outon MH202010024); the values come from the two live plants above.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