Skip to content

Rewrite with UI, calibration, and syncing with physical switches#51

Merged
Sese-Schneider merged 70 commits intoSese-Schneider:mainfrom
clintongormley:feat/ui-sync-rewrite
Mar 5, 2026
Merged

Rewrite with UI, calibration, and syncing with physical switches#51
Sese-Schneider merged 70 commits intoSese-Schneider:mainfrom
clintongormley:feat/ui-sync-rewrite

Conversation

@clintongormley
Copy link
Contributor

@clintongormley clintongormley commented Feb 25, 2026

Summary

Major rewrite that transforms Cover Time Based from a YAML-only integration into a modern UI-first Home Assistant helper with comprehensive configuration, calibration, and monitoring capabilities.

Architecture

  • Config flow & UI setup: One-step config flow creates cover helpers via the UI; YAML deprecated with repair issue
  • Control modes: Single control_mode setting replaces device_type/input_mode — supports wrapped, switch, pulse, and toggle modes
  • Input mode subclasses: CoverTimeBased base class with SwitchModeCover, PulseModeCover, ToggleModeCover, and WrappedCoverTimeBased subclasses
  • Local TravelCalculator: Replaced external xknx dependency with a local HA-convention copy (zero external dependencies)

UI & Configuration

  • Lovelace configuration card: Full card for configuring control mode, entities, timing, tilt, calibration, and position reset — all via WebSocket API
  • Calibration system: Interactive UI for measuring travel time, tilt time, motor startup delay, and minimum movement time with automated step tests
  • Translatable frontend: All card strings embedded in JS with translations for EN, PT, PL

Tilt Strategies

  • Three pluggable tilt modes: inline (synchronized), sequential (close-then-tilt), and dual_motor (separate tilt motor with safety guards)
  • Tilt planning: Automatic tilt pre-step before travel (move to safe tilt → travel → restore tilt) with dual_motor support
  • Tilt zone enforcement: Skips tilt restore when target position is outside allowed tilt zone

State Monitoring & Position Tracking

  • External state monitoring: Detects physical switch/button presses and keeps position tracker in sync, with echo filtering and per-mode tilt handling
  • Calibration UI buttons: Open/stop/close buttons in calibration screen clear position to Unknown (since we can't know when the physical button was pressed)
  • Physical buttons: Wall switches, remotes, and HA raw buttons are tracked normally by the position tracker
  • Background pulse completion: Pulse/toggle commands return immediately while relay pulse finishes in background

Test plan

  • python -m pytest tests/ -v — 667 tests passing
  • python -m pytest tests/ --cov — 99.95% coverage
  • ruff check . && ruff format --check . — clean
  • pyright — 0 errors
  • pylint — 9.84/10
  • Manual testing with physical covers (switch, pulse, toggle modes)
  • Manual testing of Lovelace card configuration flow
  • Manual testing of calibration workflows
  • Manual testing of wrapped cover mode

🤖 Generated with Claude Code

Closes #53
Closes #47
Closes #43
Closes #42
Closes #39
Closes #29
Closes #31
Closes #14
Closes #6
Closes #2

clintongormley and others added 30 commits February 21, 2026 22:26
- Add pyproject.toml with pytest, ruff, and pyright configuration
- Add GitHub Actions workflow for tests and linting
- Update .gitignore with .coverage and .iml entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract the core cover logic from the monolithic cover.py into
cover_base.py (CoverTimeBased base class) with abstract methods for
relay control. Add concrete subclasses for each input mode:
- cover_switch_mode.py: separate open/close switches
- cover_toggle_mode.py: single toggle switch with directional state
- cover_pulse_mode.py: momentary pulse switch control
- cover_wrapped.py: wraps an existing HA cover entity
- cover_switch.py: helper for switch entity operations

Slim cover.py to a thin factory that creates the appropriate subclass
based on the input_mode configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a standalone travel_calculator.py that follows HA conventions,
removing the xknx library dependency. Handles position tracking,
movement timing, and travel state management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace tilt mode strings with a strategy pattern. Each strategy
encapsulates movement planning and command generation:
- base.py: TiltStrategy ABC with MovementStep dataclasses
- sequential.py: SequentialTilt with phased movement model
- dual_motor.py: DualMotorTilt with boundary lock and safety
- inline.py: InlineTilt using the main motor for tilt control

Factory function creates the appropriate strategy from config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add __init__.py with async_setup_entry for config entry support
- Add config_flow.py with UI-based setup wizard
- Update manifest.json with config_flow dependency and version bump

Enables UI-first setup, deprecating YAML-only configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add calibration.py with automated timing calibration services:
- start_calibration/stop_calibration for travel time measurement
- Motor overhead calibration with two-phase step test
- Min movement time calibration with incremental pulses
- Tilt time calibration with travel_moves_with_tilt validation
- CalibrationState dataclass for tracking active calibration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add websocket_api.py providing real-time configuration management:
- get_config/save_config for cover timing parameters
- get_all_covers for listing configured covers with state
- start/stop_calibration commands with progress reporting
- set_known_position/set_known_tilt_position for position reset
- raw_command for direct relay control bypassing position tracker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update services.yaml with calibration and position reset services
  using target selectors for UI mode
- Update strings.json with config flow, calibration, and service
  descriptions
- Update translations for en, pt; add pl translation
- Add translatable strings for all calibration attributes and
  frontend card labels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add cover-time-based-card.js (1599 lines) providing a full
configuration UI:
- Device tab: entity pickers, timing table with save/discard,
  input mode and tilt mode selectors
- Calibration tab: automated calibration with progress feedback,
  position reset, and per-attribute hints
- Tilt Motor tab: strategy-specific entity pickers and config
- All strings are translatable via HA's localization system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 19 test files with ~10,000 lines of tests covering:
- Base movement orchestration and position tracking
- All input mode subclasses (pulse, switch, toggle, wrapped)
- Cover factory and service dispatching
- Calibration system with all calibration types
- WebSocket API endpoints and config management
- Tilt strategy behavior for all strategy types
- State monitoring with echo filtering
- Travel calculator position/timing logic
- Config flow and integration setup
- Relay command sequences and motor overhead

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rewrite README for UI-first setup, deprecate YAML-only docs
- Add sections for calibration, tilt strategies, state monitoring
- Update CHANGELOG with all new features and breaking changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 20 design and plan documents covering:
- UI config flow design and implementation plan
- Input mode subclasses design and refactor plan
- State monitoring design and implementation plan
- Calibration APIs design and implementation plan
- Configuration card design and plan
- Manual position reset design and plan
- Tilt strategies design, refactor plan, and config UI plan
- Inline tilt design and implementation plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When travel or tilt position is None (e.g. newly created entity with no
restored state), None != 0 evaluates to True, causing set_position(100)
to silently force tilt to 100% on unknown state. Add the same None guard
that DualMotorTilt already has.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move CONF_*, DOMAIN, and DEFAULT_* constants that were duplicated
across cover_base.py, cover.py, websocket_api.py and __init__.py
into a single const.py module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move _resolve_entity from cover.py and websocket_api.py into a shared
helpers.py module as resolve_entity (raises) and resolve_entity_or_none
(returns None). Update all call sites and test mock paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move 10 calibration methods (337 lines) from CoverTimeBased into
CalibrationMixin in cover_calibration.py. CoverTimeBased now inherits
from CalibrationMixin, keeping all behavior identical.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract _extract_coupled_tilt, _extract_coupled_travel, and
_calculate_pre_step_delay from CoverTimeBased into module-level
functions in tilt_strategies/planning.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract the ~45-line tilt coupling block duplicated between
_async_move_to_endpoint and set_position into _plan_tilt_for_travel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reorganize definitions across 5 files for top-to-bottom readability:
- cover_base.py: group by lifecycle/properties/public API/internals
- cover_toggle_mode.py: public overrides → state handlers → relay cmds
- dual_motor.py: move can_calibrate_tilt after __init__
- test_websocket_api.py: group unwraps at top, classes match source order
- cover.py: group constants, move _register_services after setup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pulse/toggle send methods now return immediately after the ON edge,
deferring the sleep+turn_off to a background task. This fixes calibration
overhead measuring as zero and improves position tracking accuracy.

Also adds _send_tilt_open/close/stop overrides to PulseModeCover and
ToggleModeCover so tilt buttons use the same pulse/toggle pattern as
travel buttons. Fixes ToggleModeCover.async_stop_cover missing the
_send_tilt_stop call when tilt restore/pre-step was active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ration

_async_handle_command dispatches to _send_open/_send_close but never set
_last_command. The higher-level methods (async_open/close_cover) set it,
but calibration calls _async_handle_command directly. Toggle mode's
_send_stop needs _last_command to know which button to re-pulse, so it
silently skipped every stop during calibration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… switch for pulse mode

Replace the two-field device_type + input_mode system with a single
control_mode field (wrapped/switch/pulse/toggle). No migration needed
as this code hasn't been released yet.

Pulse mode now requires a stop switch entity — the entity shows as
unavailable in HA and the calibration tab is disabled until configured.
Tilt stop switch is also required for pulse mode with dual_motor tilt.

Tilt entity visibility now follows control mode:
- wrapped: no tilt entity pickers
- switch/toggle: tilt open + close
- pulse: tilt open + close + stop (required)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change min=0 to min=0.1 for travel_time_close, travel_time_open,
tilt_time_close, and tilt_time_open. Matches the YAML schema's
cv.positive_float behavior and prevents division-by-zero in the
position calculator and calibration logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix _async_move_tilt_to_endpoint and set_tilt_position to use tilt
  motor relays instead of main motor in dual_motor mode
- Add tilt motor stops at intermediate cleanup points for safety
- Unify _has_tilt_motor() to check tilt_strategy.uses_tilt_motor
  consistently between base and wrapped classes
- Map legacy travel_moves_with_tilt YAML config to inline tilt mode
- Fix frontend auto-save entity_id override order
- Add assertion for _start_pending_travel command narrowing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Declare host class attributes and methods under TYPE_CHECKING so pyright
can resolve them without runtime circular imports. Adds assertions for
str | None narrowing where values are guaranteed set by callers.

Resolves all 39 reportAttributeAccessIssue pyright errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HACS rejects the non-standard "card" key in strings.json. Move all card
UI translations into the JS bundle and look up locale via hass.language.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The pytest_homeassistant_custom_component verify_cleanup fixture
detects pending _complete_pulse() tasks after tests that intentionally
skip draining. Add _cancel_tasks() cleanup to the 6 affected tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
clintongormley and others added 9 commits February 25, 2026 17:15
ON→OFF transitions are just relay releases and should be ignored.
Only the rising edge (OFF→ON) indicates a real button press.
Applies to both travel and tilt external state handlers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover services (open/close/stop) now behave identically regardless of
control mode. No special-casing where same-direction = stop in toggle
mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pressing open/close when tracker already shows the endpoint still
sends the relay command plus run-on time. This allows physical
covers that are out of sync to resync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tilt calibration now derives step size from num_steps (3 steps → 20%
each) instead of hardcoding 10%. Travel calibration (8 steps → 10%)
is unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses pytest-homeassistant-custom-component for a real HA instance.
input_boolean entities simulate physical switches. MockConfigEntry
loads the integration through the real config entry lifecycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests open/track/auto-stop, stop-during-movement, set_position,
and endpoint resync through real HA service calls, event bus,
and switch state changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Feedback: echo filtering and external button press detection
- Modes: toggle stop-before-reverse and pulse relay pulsing
- Lifecycle: correct entity features from config, position restore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests tilt-before-travel pre-step and tilt rejection when
cover is not at an endpoint position.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consolidates the homeassistant component setup (turn_on/turn_off services)
into the shared fixture so individual tests don't need to do it separately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@clintongormley
Copy link
Contributor Author

Hey @clintongormley Thank you so much for your work on this! I will try to test and review as soon as I get to it, however this is a huge PR, so I probably have to do it on a free day on an upcomming weekend.

No worries. I'm going to keep pushing changes to this branch.

clintongormley and others added 7 commits February 25, 2026 19:16
test_close_while_closing_reissues and test_open_while_opening_reissues
called async_close/open_cover which creates _complete_pulse tasks, but
never cancelled them before the test ended, causing "Task was destroyed
but it is pending" errors in CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests that leave the cover moving (opening/closing) caused "Lingering
timer" CI failures because the auto_updater_hook (0.1s interval) was
still scheduled when the test ended.

Fix: setup_cover fixture now yields and unloads the entry on teardown.
Tests creating their own entries also unload them at the end.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tFound

- Add stop_auto_updater() to async_will_remove_from_hass() so the 0.1s
  interval timer is cancelled when the entity is removed (fixes lingering
  timer errors in test teardown)
- Force homeassistant component setup in test fixture by clearing
  pre-loaded state, ensuring turn_on/turn_off services are registered
  (fixes ServiceNotFound on CI Python 3.13)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HA 2026.2.3 requires Python >=3.13.2, so 3.12 installs an incompatible
version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrapped covers produce 2 state transitions on direction change (e.g.
closing→open→opening). Mark expected transition count correctly and
move same-state check before echo filter so attribute-only updates
don't consume echo counts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move step_count increment to before the step movement so the step
number appears immediately when each step begins. Show "Final step"
during the continuous phase of startup delay calibration. Also
restructure the calibration active box layout (name, step, buttons
on separate lines).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@clintongormley
Copy link
Contributor Author

Hi @Sese-Schneider

Have you had a moment to look at this?

thanks

Copy link
Owner

@Sese-Schneider Sese-Schneider left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clintongormley thank you so much for the complete rewrite! This will be such a game-changer.

The code does generally LGTM, however its impossible for me to spot bugs in such a large rewrite.
After addressing my changes, I would like to release this as 4.0.0-beta.1 so the community can give feedback on this!
Can you please let me know all the issues/feature-requests this also fixes so we can close them with the PR?

Thanks again!

entity_id:
example: cover.blinds
position:
tilt_position:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clintongormley is this valid?

clintongormley and others added 4 commits March 4, 2026 12:55
Co-authored-by: Sebastian Schneider <sese.tailor@gmx.net>
Co-authored-by: Sebastian Schneider <sese.tailor@gmx.net>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a ha-version matrix so the test suite runs against both the
current stable release and the latest dev/beta pre-release of
pytest-homeassistant-custom-component. The dev job is continue-on-error
so failures are an early warning without blocking merges.

Also switches all pip calls to python -m pip for interpreter consistency,
and scopes --pre to pytest-homeassistant-custom-component only so that
pytest/pytest-asyncio stay on stable releases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@clintongormley
Copy link
Contributor Author

@clintongormley thank you so much for the complete rewrite! This will be such a game-changer.

thanks for the review and approval @Sese-Schneider !

The code does generally LGTM, however its impossible for me to spot bugs in such a large rewrite.

You didn't review every line??? Shock! I tried to do it in smaller steps but in the end it was pretty much a rewrite where lots of moving parts have to be added together. Thanks for the trust

I'm sure there will be plenty of bugs that will come out of the woodwork as soon as this is released.

After addressing my changes, I would like to release this as 4.0.0-beta.1 so the community can give feedback on this! Can you please let me know all the issues/feature-requests this also fixes so we can close them with the PR?

I've marked all the issues to be closed when this is merged, and I've deleted the docs/plans/. I've also added a commit to test against stable as well as dev.

Thanks again!

And to you :)

I think we're good to go!

@Sese-Schneider Sese-Schneider linked an issue Mar 5, 2026 that may be closed by this pull request
@Sese-Schneider Sese-Schneider merged commit c6ae4ea into Sese-Schneider:main Mar 5, 2026
5 checks passed
@clintongormley clintongormley deleted the feat/ui-sync-rewrite branch March 8, 2026 19:49
@clintongormley
Copy link
Contributor Author

@tykarol have you had a chance to try out the 4.0 beta release? I'm keen to get your feedback on how your blinds work with the tilt mechanism

@tykarol
Copy link
Contributor

tykarol commented Mar 10, 2026

@clintongormley no, I have this on the different hardware and not connected to the real devices atm. I will try to find some time to try it on HA UI with the relays at lest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment