Repository navigation
fix(climate,cover): restore the setpoint with its knob offset; scope cover members settle from unknown (#791 / #795 follow-up) - #820
Merged
Conversation
… the nominal (OpenWebNet-HA#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, `*michnovka#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>
…ir travel timer too (OpenWebNet-HA#795 follow-up) On the main bus (and for a general frame, which crosses an F422 natively) an area / group cover sends one scope frame; the gateway relays it to the members, which took it as an external run. A member with no position then froze to no position when its F411/2 stopped itself before `travel_time`, and stayed unknown until another event anchored it, while the same member commanded directly settles on travel_time + 2 s. The class comment "the members settle their own runs" held only for the F422 fan-out path, and the release note and docs promised the settle for the open / close buttons of every timed cover. The state setup of _async_move becomes _begin_run and the settle of _async_run_to_end becomes _settle_run_to_end. A scope open / close / set_cover_position 0 / 100 without fan-out opens the run of every member that _runs_to_end_from_unknown before the frame is queued, tracks the write on each (a lost frame aborts their runs as an own command's would) and settles them on their travel timer; the relayed scope frame is their motor-start echo. Members with a known position keep timing the run from it, as for a direct command. A scope stop cancels the members' settles first, as a direct stop does, so the relayed stop ends their run with the position still unknown instead of being taken for the actuator's own early stop. Docs and release note say so; the stale "(default 50)" comment in the restore test says "(preset to 50)". Tests: early actuator stop after a scope open, a known member next to an unknown one on a scope close, a scope frame refused at once and one lost after queueing, set_cover_position 0 on the scope, a scope stop during the settle. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
GreenGrassBlueOcean
approved these changes
Oct 26, 2026
GreenGrassBlueOcean
left a comment
Contributor
There was a problem hiding this comment.
@gdluck Confirmed on both readings — both are genuine gaps in #791 / #795 and both fixes are sound:
-
Climate restore with
local_offset(5d97429,#791follow-up):- Confirmed: since
#791stores_local_target_temperature(which already includeslocal_offset) instate.attributes["temperature"], restoring it intoself._target_temperaturewith_local_offset = 0and_local_target_reported = Falsecaused the first post-restart*#4*Z*13*02##to double-add the offset (23.5 + 2 = 25.5 °C) and caused an immediateasync_set_temperaturebefore Dim 13 arrived to subtract0instead of2. - Restoring
self._local_offset = int(float(last_state.attributes.get("local_offset") or 0))(consistent with integer Dim 13 knob codes-3..+3inownkb:chunk:r000582andf"{self._local_offset:+d}"),self._target_temperature = shown - self._local_offset, andself._local_target_temperature = shownwith_local_target_reported = Falsematches the live state machine across Dim 13, Dim 14, Dim 12, negative offsets, andHVACMode.OFF.
- Confirmed: since
-
Main-bus scope cover members settling from
unknown(bf1d50a,#795follow-up):- Confirmed: without F422 interface fan-out,
MyHOMEScopeCoversends a single scope frame (*2*1*1##/*2*2*0##), andrelay_scope/relay_generalpreviously routed that relayed frame to unknown-position timed members through the external-run branch (_start_position = None,_end_run_from_unknown = False,_stop_task = None), leaving early-stoppingF411/2actuators frozen atunknowninstead of settling attravel_time + END_SETTLE_MARGIN. - Pre-opening
_runs_to_end_from_unknown(opening)members via_begin_run(direction), aborting on send failure, arming_follow_scope_run_to_end(opening, written), and clearing_end_run_from_unknown/ cancelling member_stop_tasks inasync_stop_coverbehaves cleanly across echo-assisted, non-echo, and user-stopped runs. - (Two minor non-blocking notes, both fine as-is:
except Exception:in_async_run_members_to_endmirrorsMyHOMECover._async_move, and known-position timed members onarea.set_cover_position(0/100)keep pre-existing#702behavior.)
- Confirmed: without F422 interface fan-out,
We are merging #822 → #820 → #817 now, leaving a per-item review on #821, and will rebase/un-draft #816 once OpenWebNet-Encyclopedia#52 lands (own-firmware-oracle#65 is now merged).
GreenGrassBlueOcean
merged commit Oct 26, 2026
5d3e6ed
into
OpenWebNet-HA:v2-phase1-architecture
17 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by A
Before: after a Home Assistant restart a climate zone whose knob offset is not zero showed the saved setpoint plus the offset a second time (23.5 saved, offset +2 → 25.5 shown) until the next dimension 12 / 14 report; a scope cover's open / close on the main bus left its timed member covers at
unknownwhen the actuator stopped itself beforetravel_time, while the same member commanded directly settled to 100 %.After: the restore path applies the #791 rule (the saved value is the setpoint in force, the nominal is that less the saved offset) and the members of a main-bus scope command settle on their travel timer exactly as a direct command does.
What is the problem
Two follow-ups on merged PRs of mine, found by an adversarial re-read of our PRs after @GreenGrassBlueOcean's #140 review on OWNd showed what our verification was missing. Both are code paths the live captures never exercised. @GreenGrassBlueOcean could you confirm the readings (or say if you disagree), especially the scope-cover one?
async_restore_last_state). Context: fix(climate): show the setpoint in force (dimension 12), keep dimension 14 as the commanded value #791 made the slider show the setpoint in force (dimension 12, knob offset included) and stopped dimension 13 from adding the offset on top of it, gated by_local_target_reported. How it was found: reading the restore path against the live handlers instead of the PR's claim. The savedtemperature(now always an offset-included value) was restored into_target_temperature, the nominal slot, with the flag False and the offset 0, andlocal_offset/local_target_temperaturewere saved but never read back; so the first*#4*2*13*02##after a restart ran the samenominal + offsetline the live path had just stopped running on a dimension-12 value. A scratch replay showed 23.5 restored → 25.5 shown; the live path shows 23.5.travel_time + 2 seven when the actuator's own stop status comes earlier, and set_settles_open_close_from_unknown = Falseon the scope cover with the comment "the members settle their own runs". How it was found: following the one frame a main-bus area / group cover sends into its members. The gateway relays*2*1*1##to them, the direction branch ofhandle_eventtakes it as an external run (_start_position None,_end_run_from_unknown False), so an early stop at 5 s froze the position toNoneand the member stayedunknownuntil another event anchored it. The class comment only holds on the F422 fan-out path, where the members run their ownasync_open_cover; the release note and docs promised the settle for "the open / close buttons" of every timed cover.Fixes nothing tracked in an issue; both are regressions of intent in #791 and #795.
What is the solution
5d97429):local_offsetis restored, the nominal becomesshown - offset, the shown value goes into the local-target slot; the flag stays False so a dimension 14 before the next dimension 12 behaves as on a fresh entity; a state without a usable offset is read as before.bf1d50a):_async_move's state setup is now_begin_run, the settle of_async_run_to_endis_settle_run_to_end;set_cover_position 0 / 100without F422 fan-out opens the run of every member that_runs_to_end_from_unknownbefore the single scope frame is queued, tracks the write on each (a lost frame aborts their runs as an own command's would) and settles them on their travel timer; the relayed scope frame is their motor-start echo (_handle_echomatches on direction, not WHERE);State Diagram
stateDiagram-v2 direction LR state "Restart: saved temperature 23.5, local_offset 2" as Saved state "Before: nominal 23.5, offset 0" as Before state "After: nominal 21.5, local 23.5, offset 2" as After state "*#4*2*13*02## arrives" as Dim13 Saved --> Before : old restore Saved --> After : this PR Before --> Dim13 After --> Dim13 Dim13 --> Shown25 : nominal + offset = 25.5 (wrong) Dim13 --> Shown23 : nominal + offset = 23.5 (as live) state "slider 25.5" as Shown25 state "slider 23.5" as Shown23Hardware Verification
*#4*Z*13*00##(zone 2 on the F461; zones 35, 43, 47, 60 on the MyHomeServer1), so the double-offset restore cannot be shown on these plants; the proof is the replay test below, which fails on the base and passes after.travel_time; the test plant has a single cover (05), so N/A on hardware; the two scratch replays that found it are now the regression tests.How is it tested
tests/test_climate_setpoint_in_force.py::test_restored_setpoint_in_force_does_not_get_the_offset_added_again,::test_restored_setpoint_follows_the_live_rule_before_dimension_12(fail on the base: 3 failures incl. a parametrised case; pass after), plus the no-offset / unreadable-offset cases (+7 tests).tests/test_cover_unknown_start.py(+6 tests: scope open on the main bus with an early stop settles every unknown member, known-position members unchanged, a lost scope frame aborts the members' runs, a scope stop leaves them unknown, a refused frame; 5 of 6 fail on the base);tests/test_issue_433_cover_scope.py(settle cleanup in two existing tests).pytest tests -n 4 --cov=custom_components/myhome:5004 passed, 1 skipped,TOTAL 16845 0 100.0%;ruff check .clean;scripts/typing_ratchet.pymypy --strict: 0 error(s);scripts/verify_ha_standards.pySUCCESS;scripts/sync_documentation.py --checkSUCCESS;scripts/manage_translations.py checkno drift._begin_run→ send → relayed echo →_track_writeconfirmed; its four remarks are all failure / cancellation windows where the scope path now behaves exactly as the direct-command path already does (re-arm of an already-settling member, a settle cancelled before a lost stop write, aCancelledErrorin the one-await queueing window, a stop racing a pending send); left as is and documented in the tests.Where are the traces
tests/fixtures/traces/f461_live_2026-10-24/(runrun4_preset_templates, the dimension 12 / 13 / 14 frames the restore test replays); the cover change has no trace (no plant with an area cover and an early-stopping F411/2 in the corpus).What does the Firmware Oracle / Encyclopedia say
WHO = 4, Dimensions 12 / 13 / 14;WHO = 2,WHAT 1 / 2on an area / group WHERE):🤖 Generated with Claude Code