Skip to content

fix(cover): settle a full run at the actuator's own stop up to 0.5 s before the travel time (#770 end-stop follow-up) - #847

Merged
GreenGrassBlueOcean merged 2 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/cover-settle-from-known-position
Nov 3, 2026
Merged

GreenGrassBlueOcean merged 2 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
gdluck:fix/cover-settle-from-known-position

Conversation

@gdluck

@gdluck gdluck commented Nov 3, 2026 •

Copy link
Copy Markdown

Requested by A

What is the problem

Timed covers (F411/2 and the like, no position report) settle a full run on travel_time + 2 s since #834, as asked in discussion #770, comment 19245565 ("open/close buttons should also settle after travel time + 2 s … With the 1 minute setting the Fixglas stopped right at the end and was not reported as closed"). What was still missing is the actuator's own stop status that arrives a little before the stored travel time: cover.py::_freeze_position took a stop as the end stop only when at >= run_started_at + travel, so the F411's limit stop 60–80 ms early cancelled the settle timer and froze the timed estimate one or two percent short of the end.

Measured on the live F461 2.0.11 / F411/2 plant (internal report 20, 2026-10-26; travel calibrated on the bus at 5.82 s down / 5.85 s up): close_cover from 100 % → actuator *2*0*05## 5.76 s after the motor start → entity open 1, last_run_seconds 5.76; open_cover → stop at 5.74 s (report 18) → open 98; the one run that matched 5.82 s → open 100. The shutter was physically on its end stop every time. With a yaml travel_time: 6 (whole seconds) the same run ends at 4 %.

Affects WHO 2 timed covers on every gateway; the 60 s / 86 s F411 shutters of #770 (MH200N) are the reported case.

Known ambiguity, stated up front: on a gateway that relays no WHO 2 command translation (MH200N and the like) a wall-switch or scenario stop in the last 0.5 s of a full run is the same plain *2*0*WHERE## as the actuator's limit stop, so it is reported as arrived too. Worst case a manual stop there shows 100 % / 0 % instead of 92..99 % on a 6 s shutter (0.5 s = 8 % of the travel) and instead of 99 % on a 60 s one. That is weighed against what the tolerance fixes on every full run: the measured 60–80 ms early stop (reports 20 / 18) plus the whole-second travel_time rounding (up to 0.5 s). Stops HA sends (stop_cover, the auto-stop of a set_cover_position run) and translated keypad stops (*2*1000#0*WHERE##, F461 / MyHomeServer1) are never snapped. 0.5 s is kept: it stays inside the 1 s early window of the #843 calibration cutoff rule (CALIBRATION_CUTOFF_EARLY, DECISIONS_REGISTER D45), the fix for #837, and treats no configurator limit as a stop. This PR does not touch calibrate_cover: #837 is settled by #843, and the settle tolerance here only applies to runs, never to calibration.

Fixes the end-stop follow-up of discussion #770 (comment 19245565); no issue number.


What is the solution

  1. cover_motion.py: END_STOP_TOLERANCE = 0.5 s (measurement cited in the comment: 60 / 80 ms early stops on a 5.82 s calibrated travel; the stop-status relay plus the limit stop ≈ 0.1 s; a whole-second yaml travel_time can be up to 0.5 s above the real travel).
  2. cover.py:
  3. Docs: docs/configuration/covers.md (full-run bullet) and .github/release-notes/2.0.0b17.md.

State Diagram

stateDiagram-v2
    [*] --> Known : position 0..100
    Known --> FullRun : open_cover or close_cover or set 0|100 or scope or wall direction
    FullRun --> FullRun : *2*0*W## before travel - 0.5 s (plain) -> freeze estimate (mid-run stop)
    FullRun --> End : *2*0*W## at travel - 0.5 s .. travel (plain, actuator's own) -> 0 % or 100 %
    FullRun --> End : *2*0*W## at or after travel -> 0 % or 100 %
    FullRun --> End : travel + 2 s timer, no stop seen (#834)
    FullRun --> Frozen : *2*1000#0*W## (translated keypad or HA stop) or own stop_cover -> estimate
    Known --> PartialRun : set_cover_position (HA auto-stop)
    PartialRun --> Frozen : any stop -> estimate (tolerance never applies)
    [*] --> Unknown : no restored position
    Unknown --> SettlingFromUnknown : open or close
    SettlingFromUnknown --> SettlingFromUnknown : *2*0*W## before travel -> ignored, timer continues (#795)
    SettlingFromUnknown --> End : timer travel + 2 s, or stop at/after travel
Loading

Hardware Verification

  • Live F461 2.0.11 with an F411/2 at WHERE 05 (internal report 20, 2026-10-26, HA driven through the REST API, bus observed on a second OpenWebNet session): the 5.76 s close from 100 and the 5.74 s open of reports 20 / 18 are the measurements this change is built on; the shutter was on its limit stop both times while HA showed open 1 / open 98. The branch itself has not been run on the plant yet (no gateway contact in this session); the test file replays the report's timings.
  • Not verified on the MH200N + F411 M=0 / M=1 shutters of 🔬 MyHOME v2.0.0b16 Pre-Release: having a firm conversation with the firmware #770 (60 s "Essen Fixglas", 86 s "Stiege oben"); the user is the one who can confirm the "stopped right at the end and was not reported as closed" case now reads closed.

How is it tested

  • tests/test_discussion_770_cover_end_stop_tolerance.py (new, 16 tests, F461 fixtures with travel 5.82 / 5.85 s, direction echo 0.12 s):
    • test_close_from_100_with_the_actuator_stop_60_ms_early_is_closed (report 20: 5.76 s → closed 0, last_run_seconds 5.76, only *2*2*05## sent, settle task gone)
    • test_open_from_0_with_the_actuator_stop_80_ms_early_is_open_100 (report 18: 5.74 s → 100)
    • test_a_stop_at_the_travel_time_is_still_the_end_stop
    • test_a_stop_further_than_the_tolerance_from_the_end_freezes_the_estimate (0.51 s early → 9 %), test_a_stop_exactly_at_the_tolerance_is_the_end_stop
    • test_a_translated_keypad_stop_near_the_end_freezes_the_estimate, test_an_ha_stop_near_the_end_freezes_the_estimate (asserts _full_run_to_end is cleared by stop_cover before the stop status), test_a_plain_stop_during_a_partial_run_near_the_end_freezes_the_estimate
    • test_set_position_0_to_95_ends_at_95_not_at_the_end (travel 6 s, HA's own stop at 5.7 s → 95 %), test_a_plain_stop_at_the_target_of_a_0_to_95_run_is_not_an_end_stop (plain stop at 5.7 s of that run → 95 %, not 100 %)
    • The manual-stop ambiguity above is the one case the tests do not and cannot distinguish: test_a_wall_switch_close_from_100_settles_at_the_early_actuator_stop is the same frame sequence whether the stop was the actuator's or a keypad's.
    • test_a_wall_switch_close_from_100_settles_at_the_early_actuator_stop
    • test_a_run_from_unknown_keeps_settling_on_the_timer, test_the_timer_still_settles_a_run_whose_actuator_never_stops
    • test_discussion_770_f411_cutoff_just_before_the_stored_travel_time_is_closed[Essen Fixglas / Stiege oben]
    • test_a_known_scope_member_settles_at_its_early_actuator_stop
  • Unchanged: tests/test_cover_unknown_start.py, tests/test_discussion_770_cover_full_run_settle.py, tests/test_cover_*.py, tests/test_issue_302.py, tests/test_issue_433_cover_scope.py (198 passed).
  • Full suite with OWNd master on PYTHONPATH: 5202 passed in 442.70 s (5 snapshots passed; the base's 5188 + 14 new); branch coverage of cover.py / cover_motion.py: cover.py 1815 statements, 0 missed, 698 branches, 24 partial (99.0 %); cover_motion.py 44 statements, 0 missed, 16 branches, 1 partial (98.3 %). None of the partial branches (cover.py 194->196, 519->524, 756->758, 900->exit, 1007->exit, 1053->1080, 1055->1080, 1082->1087, 1118->exit, 1295->1297, 1322->exit, 1324->exit, 1446->1449, 1477->1480, 1533->1535, 2098->2104, 2236->2238, 2338->2340, 2368->2390, 2383->2385, 3038->exit, 3041->3045, 3109->3111, 3228->3262; cover_motion.py 137->139) is on a changed line; they are unchanged base code (measured with a plain --cov-branch, not the repository's CI coverage configuration).
  • ruff check . clean; mypy on the two modules clean; scripts/verify_ha_standards.py and scripts/sync_documentation.py --check pass.

Where are the traces

  • The timings come from the live F461 2.0.11 capture of internal report 20 (2026-10-26, cover 05: *2*2*05## 19:09:06.541 → echoes *2*1000#0*05##, *2*0*05##, *2*1000#2*05##, *2*2*05## → actuator *2*0*05## 19:09:12.462) and report 18; they are cited frame by frame in the test file. No new fixture is added under tests/fixtures/traces/: the raw capture of report 20 is not in the repository, and the existing F461 fixtures (tests/fixtures/traces/f461_live_2026-10-23, with the 5.825 s cover reply of its stopwatch run, f461_live_2026-10-24, f461_actuators_live_2026-10-25) do not contain a full run from a known position.
  • Discussion 🔬 MyHOME v2.0.0b16 Pre-Release: having a firm conversation with the firmware #770 traces of the F454 + F401 (tests/fixtures/traces/discussion_770_f454_f401) are position-reporting covers and do not exercise this path.

What does the Firmware Oracle / Encyclopedia say

  1. OpenWebNet Encyclopedia (WHO = 2): *2*0*WHERE## is the automation status "stop"; the specification gives no way to tell the actuator's own limit stop from a keypad stop without the WHO 2 command translation (*2*1000#0*WHERE##, relayed by F461 / MyHomeServer1 for commands, never for the actuator's own stop). That is why the tolerance applies to untranslated stops only.
  2. Firmware Oracle: N/A for the timing (the oracle records ACK / NACK / forwarding verdicts, not stop latencies); the 60–80 ms early stop is a plant measurement (report 20 / 18 on F461 2.0.11), not an oracle verdict.

Checklist

  • PR Description: Follows the structured sections above.
  • Trace-Backed (if protocol/device change): Behaviour is built on the measured F461 / F411/2 runs of reports 20 and 18 (timings replayed in the tests), not guessed; no new bus fixture.
  • V2 Architecture: No new entity; MyHOMECover.handle_event() is the only event path touched.
  • Tests & Linters: pytest, ruff, and scripts/verify_ha_standards.py pass (with OWNd master on the path).

🤖 Generated with Claude Code

gdluck and others added 2 commits November 3, 2026 09:51
…before the travel time (OpenWebNet-HA#770)

Discussion OpenWebNet-HA#770 (comment 19245565) asks that open_cover / close_cover on timed
covers settle at the end like set_cover_position 0 / 100; OpenWebNet-HA#834 settles them
on travel_time + 2 s, but the actuator's own stop status that arrives a little
before the stored travel time still froze the timed estimate short of the end:
_freeze_position took a stop as the end stop only at or after
run_started_at + travel.

Measured on the live F461 2.0.11 / F411/2 plant (report 20, 2026-10-26,
travel calibrated at 5.82 s down / 5.85 s up): close from 100 %, actuator
*2*0*05## 5.76 s after the motor start -> "open 1", last_run 5.76; open with
the stop at 5.74 s (report 18) -> "open 98"; the run that matched 5.82 s ->
100. The shutter sat on its limit stop every time. With a whole-second yaml
travel_time (6 for that shutter) the same run ends at 4 %.

- cover_motion.py: END_STOP_TOLERANCE = 0.5 s (60-80 ms measured early stop
  plus up to 0.5 s of yaml travel_time rounding; cited in the comment).
- cover.py: _freeze_position(at, *, end_stop_tolerance=0.0) counts a stop
  as the end stop when at + tolerance >= run_started_at + travel; a new
  _full_run_to_end flag marks runs to an end (own open / close and set 0 / 100,
  scope runs that move the member, wall full runs) and handle_event passes
  the tolerance for a plain *2*0*WHERE## on such a run only. Translated
  keypad stops (*2*1000#0*WHERE##), HA's own stops, stops during an HA
  partial run and stops more than 0.5 s before the travel time still freeze
  the estimate; last_run_seconds keeps the measured run; a run from unknown
  keeps settling on its timer (OpenWebNet-HA#795 / OpenWebNet-HA#820); the OpenWebNet-HA#834 timer still settles a
  run whose stop never comes.
- tests/test_discussion_770_cover_end_stop_tolerance.py: 14 tests on the
  F461 fixtures with the report-20 timings, the OpenWebNet-HA#770 F411 shutters (60 s /
  86 s cutoff just before the stored time) and a known scope member.
- docs/configuration/covers.md, .github/release-notes/2.0.0b17.md.

Verified with OWNd master (fb0acd7) on PYTHONPATH: pytest tests: 5202
passed; cover.py / cover_motion.py 100 % statements, no partial branch on a
changed line; ruff clean; mypy clean on the two modules;
scripts/verify_ha_standards.py and scripts/sync_documentation.py --check pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the manual-stop ambiguity

Review of fix/cover-settle-from-known-position:

- _full_run_to_end is set only by _settle_run_to_end (own open / close and
  set_cover_position 0 / 100, scope runs that move the member) and by
  _arm_wall_settle (wall full runs); _begin_run clears it, so a
  set_cover_position run HA times itself never gets the tolerance. _begin_stop
  now clears it as well: a stop HA asked for is never the end stop, whenever
  its status arrives (before this the write of the stop frame cleared it).
- Tests: set_cover_position 95 from 0 on a 6 s travel ends at 95 after HA's own
  stop at 5.7 s (within 0.5 s of the travel time), and a plain *2*0*05## at
  5.7 s of that run freezes at 95, not 100; the HA stop_cover test asserts the
  flag is cleared before the stop status arrives. 16 tests in the module.
- cover_motion.py END_STOP_TOLERANCE comment: a wall-switch / scenario stop in
  the last 0.5 s of a full run on a gateway without WHO 2 command translation
  (MH200N and the like) is the same plain *2*0*WHERE## as the actuator's limit
  stop and is reported as arrived; worst case 100 % / 0 % instead of 92..99 %
  on a 6 s shutter, 99 % on a 60 s one. 0.5 s kept: it covers the measured
  60-80 ms early stop (report 20 / 18) and the whole-second travel_time
  rounding, and stays inside the 1 s early window of the OpenWebNet-HA#843 calibration
  cutoff rule without treating any configurator limit as a stop.

Verified with OWNd master (fb0acd7) on PYTHONPATH: the cover modules
(test_cover_*.py, test_discussion_770_*.py, test_issue_302.py,
test_issue_433_cover_scope.py, test_component_impulse_cover.py): 257 passed;
ruff clean; mypy clean on the two modules; scripts/verify_ha_standards.py and
scripts/sync_documentation.py --check pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@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!

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

Hi @manfredgittmaier-afk,

Following up on your report in Discussion #770 regarding your MH200N + F411 (M=0 / M=1) shutters:

While #834 added the full-run settle timer (travel_time + 2 s), we noticed that when an actuator's own cutoff arrives slightly earlier than the configured travel_time (even by 50–100 ms), the entity froze the timed estimate just short of completion (e.g., at 1 % open) instead of snapping to closed.

This PR introduces an END_STOP_TOLERANCE of 0.5 s in cover_motion.py to recognize an actuator cutoff arriving right at the end of a full run as the true end stop.

Could you confirm:

  1. Is this consistent with the behavior you observed on "Essen Fixglas" (60 s) and "Stiege oben" (86 s) when using the 1-minute configurator limit (where the blind physically reached the end, but the entity stopped just short and never transitioned to closed)?
  2. If you have a chance to test this PR branch (fix/cover-settle-from-known-position), does it properly settle and report closed (0 %) on those shutters now?

(If you would like to test this branch directly in Home Assistant via SSH/Terminal:)

cd /config/custom_components
git clone -b fix/cover-settle-from-known-position --depth 1 https://github.com/gdluck/HomeAssistant-MyHOME.git /tmp/myhome_test \
  && rm -rf myhome.bak \
  && mv myhome myhome.bak \
  && cp -r /tmp/myhome_test/custom_components/myhome ./ \
  && rm -rf /tmp/myhome_test

(Restart Home Assistant after updating; revert anytime with rm -rf /config/custom_components/myhome && mv /config/custom_components/myhome.bak /config/custom_components/myhome && ha core restart)

Thank you!

@gdluck

gdluck commented Nov 3, 2026

Copy link
Copy Markdown
Author

Independent confirmation on a physical F461 2.0.11 + F411 test plant (one shutter, WHERE 05), standing in for the reporter's MH200N + F411 60 s / 86 s blinds. Driven from a second OpenWebNet client while Home Assistant ran the v2-phase1-architecture head (which already has #834's full-run settle); times read from the HA REST API.

Setup: travel_time = 6 s (the shutter's real travel), F411 actuator cutoff raised so its bus stop arrives much later than the travel, the reporter's situation at small scale.

Bus measurement (actuator cutoff, undisturbed close):

F411 at 2 minutes:  lower -> *2*0*05## at 119.95 s
F411 at ~30 s step: lower -> *2*0*05## at  30.49 s

The curtain physically reaches its end in ~5 s; the gateway only reports the stop at the cutoff (120 s, then 30 s), i.e. 25–115 s late.

Home Assistant full runs (travel_time 6 s, F411 cutoff ~30 s):

open_cover  -> current_position 100 (open)   at ~9 s   state=open
close_cover -> current_position 0   (closed) at ~9 s   state=closed

Both settle at travel_time + 2 s and reach open / closed; neither waits for the ~30 s (or 120 s) actuator stop. On the reporter's pre-#834 build the entity stayed opening / closing for the full 2 minutes.

Conclusion: the full-run settle behaviour the reporter needs is confirmed on independent F461 + F411 hardware. Their 60 s / 86 s blinds on the mandatory 2-minute F411 cutoff will reach closed / open at travel_time + 2 s instead of hanging for 120 s.

Scope note, to be precise: because the F411 cutoff here is longer than travel_time, this exercises the full-run settle (the #834 mechanism this PR builds on), not this PR's END_STOP_TOLERANCE specifically. That tolerance covers the opposite sub-case, an actuator stop arriving ~0.5 s before travel_time; reproducing that needs the cutoff set just under travel_time (e.g. cutoff ~5.8 s with travel_time 6 s), which I can run next if useful.

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

Hi @manfredgittmaier-afk,

We are merging this fix now so that it is included directly in the upcoming v2.0.0b17 beta release.

Once v2.0.0b17 is released, you can update directly through Home Assistant without needing any manual git checkout commands. Please test it on your MH200N + F411 shutters ("Essen Fixglas" and "Stiege oben") and let us know in Discussion #770 if it behaves as expected or if you run into any issues!

@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as ready for review November 3, 2026 23:17
@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 41989f0 into OpenWebNet-HA:v2-phase1-architecture Nov 3, 2026
17 checks passed
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