Repository navigation
fix(cover): confirm end_stop on full-travel bus runs and scope run timeouts - #760
Merged
GreenGrassBlueOcean merged 2 commits intoOct 20, 2026
Merged
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
GreenGrassBlueOcean
added a commit
that referenced
this pull request
Oct 21, 2026
- Count and summarize every pull request merged since 2.0.0b15, grouped by subsystem, with the expose engine (#750), slat tilt (#508) and impulse covers (#667) as the headline features. - Covers: the start-up floor is the per-cover min_motor_pulse (#708), timed runs end at the stop status (#707) and full-travel keypad runs report the end (#760); drop the "lift only" and open follow-up lines, both shipped. - New behaviour-change section: sync_time refuses half-hour time zones (#764) and advanced: true on an F411 now warns (#762). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 tasks done
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.
Follow-up to #739 addressing the two review items from @Interstellar0verdrive.
1. What is the problem?
"stop"instead of"end_stop"(custom_components/myhome/cover.py):BT-F411/2,F411/4,LN4672M2) reaches its limit switch / hardware relay cutoff at the end of a keypad run (*2*1*WHERE##or*2*2*WHERE##), the actuator emits*2*0*WHERE##(WHAT=0, identical on the wire to a manual keypad stop).MyHOMECover._freeze_position(at)unconditionally passed"stop"toself._strategy.on_run_ended(self, "stop", frozen, at)without checking whether the run had lasted at least the full directional travel time (at - run_started_at >= _travel_for(is_opening)), a full-travel keypad run reported"stop"instead of"end_stop"and never calledself._strategy.on_position_confirmed(self, frozen, "end_stop").Noneuntil an endpoint is confirmed (such asPiecewiseGeometryStrategy(known=False)intests/test_cover_strategy_contract.pyand the calibrated geometry tracker in feat: add dedicated MyHOME administration panel #374), a full-travel keypad run left the positionNone(unknown) instead of confirming100or0.100/0without confirming the position (custom_components/myhome/cover_scope.py):WHO_2.pdf §3.0.1, general (WHERE="0"), area (WHERE="1".."9","00","100"), and group (WHERE="#1"..) commands never receive a stop frame for the scopeWHEREitself. When aMyHOMEScopeCoverhas no configured member entities,_watch_run()schedules_end_runafterself._travel_for(moving == "open")._end_runcalledself._freeze_position(time.monotonic())and then overwroteself._attr_current_cover_position = self._start_position = 100 if opening else 0without advancing the freeze timestamp to_run_started_at + travelor callingself._strategy.on_position_confirmed(self, end, "end_stop").2. How is it solved?
MyHOMECover._freeze_position(at)(custom_components/myhome/cover.py):end_stopwhennot self._strategy.assumed_state,run_started_at is not None, the cover is opening or closing,(travel := self._travel_for(is_opening)) > 0, andat - run_started_at >= travel.end_stopisTrue:frozen = 100 if is_opening else 0(even whenposition_afterwould otherwise returnNonebecause the strategy had no prior known position).self._strategy.on_run_ended(self, "end_stop", frozen, at)followed byself._strategy.on_position_confirmed(self, frozen, "end_stop").MyHOMEScopeCover._end_run()(custom_components/myhome/cover_scope.py):attomax(time.monotonic(), self._run_started_at + travel)when_run_started_atis set (accounting for any queue delay between scheduling_watch_runand anchoring the motor write), so_freeze_position(at)reports("end_stop", end)and confirmsend(100or0)._end_runfires when no run anchor was set (not confirmed), explicitly invokesself._strategy.on_position_confirmed(self, end, "end_stop").tests/test_cover_strategy_contract.py):test_piecewise_strategy_drives_cover_without_cover_py_changesrecords("end_stop", 0)and("confirmed", 0, "end_stop").test_position_unknown_until_a_full_runthat a full-travel keypad run (*2*2*21##13.0 s*2*0*21##) and a memberlessMyHOMEScopeCoverrun timeout (*2*1*#7##_end_run) both transitionPiecewiseGeometryStrategy(known=False)toknown = Trueand confirm the endpoint.3. State Diagram
stateDiagram-v2 direction TB [*] --> UnknownPosition: Startup / Restart\n(strategy.known == False, position == None) state "Position Unknown (None)" as UnknownPosition state "Motor Running (Anchored at run_started_at)" as Running state "Partial Stop (elapsed < travel)" as PartialStop state "Confirmed End Stop (0% or 100%)" as ConfirmedEndStop state "Known Intermediate Position (0..100%)" as KnownPosition UnknownPosition --> Running: Keypad / Bus Move (*2*1*W## or *2*2*W##)\non_run_started(direction, start_position, at) KnownPosition --> Running: Move or Set Position\non_run_started(direction, start_position, at) Running --> PartialStop: *2*0*W## or async_stop_cover()\nwhere at - run_started_at < travel_for(opening) PartialStop --> UnknownPosition: strategy.known == False\non_run_ended("stop", None, at) PartialStop --> KnownPosition: strategy.known == True\non_run_ended("stop", frozen, at) Running --> ConfirmedEndStop: 1) *2*0*W## after at - run_started_at >= travel_for(opening)\n2) _async_run_to_end() settle timer (0% / 100%)\n3) MyHOMEScopeCover._end_run() timeout ConfirmedEndStop --> KnownPosition: on_run_ended("end_stop", 100|0, at)\non_position_confirmed(100|0, "end_stop") -> strategy.known = True4. Where are the traces (real ones)?
2.0.11+ BT-F411/2 (WHERE="05", 5 s hardware cutoff)tests/fixtures/traces/f461_live_2026-10-18/myhome_trace_F461_live_2026-10-18.jsonrun2_shutter_05_travel_and_reversal, after*2*2*05##starts a full closing run, the physicalBT-F411/2actuator emits a standard*2*0*05##stop frame onevt(dt = 5.0 safter motor start) when its hardware travel timer expires at the limit switch.2.38.9(028206) + Timed Shutters (WHERE="21","31")tests/fixtures/traces/myhomeserver1_timed_cover_echo_window.json&tests/test_issue_445_myhomeserver1_covers.py*2*1000#0*W##,*2*0*W##,*2*1000#1*W##,*2*1*W##on motor start (~0.55 s) and emits*2*0*W##when the actuator's relay drops out at the end of the run (~60 s uncalibrated cutoff or calibrated travel time).1.0.x+ Group/Area Scope Shutters (#433)tests/test_issue_433_cover_scope.py*2*1*0##,*2*1*1##,*2*1*#7##) never emit a stop frame for the scopeWHERE(WHO_2.pdf §3.0.1); only individual actuators emit*2*0*<point>##at the end of travel, so a memberless scope cover settles via_end_runafter_travel_for(opening).5. What does the Firmware Oracle / Encyclopedia say?
functional/who-2-automation/what.md&WHO_2.pdf §3.0.1):WHO=2basic movement defines onlyWHAT=0(Stop),WHAT=1(Up), andWHAT=2(Down). There is no distinctWHATcode for a hardware limit-switch stop on non-advanced shutters: when a point-to-point actuator reaches the end of its run, it transmits*2*0*WHERE##(WHAT=0).WHO_2.pdf §3.0.1, a broadcastUporDowntoGEN(0),A(area), orGR(#G) terminates with individual*2*0*<point>##frames from each physical automation object moved ("as many frames as automation objects"), and never with*2*0*0##/*2*0*A##/*2*0*#G##for the scope address itself.openwebnet-mcp—lookup_firmware_verdict):F453,F453AV,F454,F459,F460,F461,H4684,MH200,MH200N,MH202,MyHomeServer1),*2*1*31##/*2*2*31##and*2*0*31##are the sole event-stream state transitions for standardWHO=2shutters (F460,F461, andMyHomeServer1additionally emit*2*1000#1*31##/*2*1000#0*31##translation frames before the actuator status frame). Distinguishing a manual mid-travel stop from a limit-switch end stop on a standard shutter is therefore a function of whether the motor ran for at leasttravel_for_direction(opening).Verification
pytest tests/test_cover_strategy_contract.py tests/test_issue_433_cover_scope.py tests/test_component_cover.py tests/test_cover_strategies.pymypy custom_components/myhome/cover.py custom_components/myhome/cover_scope.py custom_components/myhome/cover_strategies(Success: no issues found in 8 source files)ruff check custom_components tests scriptsscripts/verify_ha_standards.py&scripts/sync_documentation.py --checkV2 Architecture: New entities implement
handle_event()and do not manually subscribe viaasync_dispatcher_connect.