Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
158 commits
Select commit Hold shift + click to select a range
92271b8
docs: add UI config flow design document
clintongormley Feb 13, 2026
154985a
docs: add UI config flow implementation plan
clintongormley Feb 13, 2026
6db0ac2
Add config_flow to manifest.json
clintongormley Feb 13, 2026
22c1b6b
Create __init__.py for Cover Time Based integration
clintongormley Feb 13, 2026
9e8ea51
Update strings.json with translations for config, options, and subent…
clintongormley Feb 13, 2026
fa3cd92
Add config_flow.py with config, options, and subentry flows
clintongormley Feb 13, 2026
0ceeff4
Add async_setup_entry to cover.py for config entry support
clintongormley Feb 13, 2026
982b7b3
refactor: simplify config flow to one entry per cover
clintongormley Feb 13, 2026
6bfc1e9
refactor: two-step config flow with conditional fields
clintongormley Feb 13, 2026
b87a803
feat: add name field, tilt section, and improve config flow UX
clintongormley Feb 14, 2026
2600c49
feat: deprecate YAML configuration with repair issue
clintongormley Feb 14, 2026
3558418
fix: address code review issues
clintongormley Feb 14, 2026
c777b00
fix: add missing description to user step in translations
clintongormley Feb 16, 2026
8e5e21d
Add input mode subclasses design doc
clintongormley Feb 15, 2026
f035c15
Add input mode subclasses implementation plan
clintongormley Feb 15, 2026
908ac3a
Add test infrastructure and characterization tests for relay commands
clintongormley Feb 15, 2026
f405949
Extract CoverTimeBased base class to cover_base.py with abstract methods
clintongormley Feb 15, 2026
6c1fcaa
Add concrete subclasses for each cover input mode
clintongormley Feb 15, 2026
fa84ca2
refactor: add factory function, clean up constructors, remove toggle …
clintongormley Feb 15, 2026
78d1c6f
fix: restore async_write_ha_state in set_known_position/set_known_til…
clintongormley Feb 15, 2026
21b25b8
refactor: extract helpers and unify movement methods in cover_base.py
clintongormley Feb 16, 2026
50a36c8
test: add comprehensive tests for base class movement orchestration
clintongormley Feb 16, 2026
fd98345
refactor: extract helpers to reduce duplication in movement methods
clintongormley Feb 17, 2026
93bc659
ci: add GitHub Actions workflow for tests and linting
clintongormley Feb 17, 2026
c9ae097
ci: set asyncio_default_fixture_loop_scope to silence deprecation war…
clintongormley Feb 17, 2026
38cf3da
test: cancel pending startup delay tasks in fixture teardown
clintongormley Feb 17, 2026
1a7420e
test: cancel _delay_task in test to prevent lingering task error
clintongormley Feb 17, 2026
9868f20
docs: add calibration APIs design document
clintongormley Feb 17, 2026
e1c0317
docs: add calibration APIs implementation plan
clintongormley Feb 17, 2026
ce496e8
refactor: merge startup/end delays into travel_motor_overhead and til…
clintongormley Feb 17, 2026
101b6cc
feat: store config entry ID on cover entity for calibration support
clintongormley Feb 17, 2026
04f91f8
feat: add CalibrationState dataclass and calibration constants
clintongormley Feb 17, 2026
a972f67
feat: implement start_calibration and stop_calibration for travel tim…
clintongormley Feb 17, 2026
65fce75
feat: add tilt time calibration with travel_moves_with_tilt validation
clintongormley Feb 17, 2026
e2d1c45
feat: implement motor overhead calibration with automated step test
clintongormley Feb 17, 2026
dde3ac7
feat: implement min_movement_time calibration with incremental pulses
clintongormley Feb 17, 2026
eba50ab
feat: add service descriptions and translations for calibration APIs
clintongormley Feb 17, 2026
a516fb6
fix: address code review findings — timeout bug, validation ordering,…
clintongormley Feb 17, 2026
601f39d
fix: use cv.make_entity_service_schema for calibration services
clintongormley Feb 17, 2026
2d73535
fix: update services.yaml with target selectors for UI mode
clintongormley Feb 17, 2026
b340c7c
feat: enable stop_calibration to return response data
clintongormley Feb 17, 2026
d5617e0
feat: two-phase motor overhead calibration with debug logging
clintongormley Feb 17, 2026
783f6fb
feat: auto-detect direction for all calibration tests
clintongormley Feb 17, 2026
4d1fdfa
fix: restrict service entity picker to cover_time_based entities
clintongormley Feb 17, 2026
575880a
fix: restrict calibration services to single entity target
clintongormley Feb 17, 2026
57c1105
feat: add direction parameter and fix field labels
clintongormley Feb 17, 2026
69b1a77
fix: rename entity_id field label to "Entity ID"
clintongormley Feb 17, 2026
de73018
feat: rename calibration attributes to close/open, add friendly labels
clintongormley Feb 17, 2026
9a3c961
feat: reset position after calibration, return cover for min_movement
clintongormley Feb 17, 2026
35db963
feat: add 5s initial pause before min_movement_time pulses
clintongormley Feb 17, 2026
3bd7833
fix: guard against race condition in position reset after calibration
clintongormley Feb 17, 2026
f619bef
feat: add WebSocket API for cover configuration
clintongormley Feb 18, 2026
91e470f
feat: add Lovelace configuration card
clintongormley Feb 18, 2026
72f17c3
fix: allow missing entity IDs in cover creation
clintongormley Feb 18, 2026
7bf48e3
fix: return cover to starting endpoint when calibration cancelled
clintongormley Feb 18, 2026
b2c0e49
docs: add configuration card design and implementation plan
clintongormley Feb 18, 2026
c320c82
refactor: replace tilt checkbox+radio with single 3-option selector
clintongormley Feb 18, 2026
7c0499c
refactor: convert device type, input mode, and tilting to dropdowns
clintongormley Feb 18, 2026
9619300
docs: add manual position reset design for calibration
clintongormley Feb 18, 2026
130fefd
docs: add manual position reset implementation plan
clintongormley Feb 18, 2026
4cf0f0f
feat: add _knownPosition state property to card
clintongormley Feb 18, 2026
5587098
feat: add Current Position section to calibration tab
clintongormley Feb 18, 2026
2c9f66a
feat: implement position reset via set_known_position services
clintongormley Feb 18, 2026
1345d58
feat: gate calibration behind known position, disable unavailable att…
clintongormley Feb 18, 2026
5c6ad6c
feat: remove timeout field, hardcode 300s, infer direction from position
clintongormley Feb 18, 2026
19fa2e3
feat: reset position to unknown on calibration finish or cancel
clintongormley Feb 18, 2026
1a62db6
feat: auto-select first enabled calibration attribute after position …
clintongormley Feb 18, 2026
35b0b8c
feat: hide position reset section during active calibration
clintongormley Feb 18, 2026
10da34a
fix: use tilt_position parameter for set_known_tilt_position service
clintongormley Feb 18, 2026
812e1e0
fix: only reset tilt position when tilt is configured
clintongormley Feb 18, 2026
b082861
feat: make timing table editable with save/discard flow
clintongormley Feb 18, 2026
60d0196
feat: switch to Device tab when changing entity
clintongormley Feb 18, 2026
a58b475
fix: rename tilt option to "Tilts with cover movement"
clintongormley Feb 18, 2026
d18db1b
feat: auto-cancel calibration when leaving screen or switching entity
clintongormley Feb 18, 2026
fd445d6
fix: calibration UI stuck after cancel, entity picker dismiss ignored
clintongormley Feb 18, 2026
a191218
refactor: remove Reset button, trigger position reset on dropdown change
clintongormley Feb 18, 2026
f40c821
feat: disable Calibration tab when required entities are missing
clintongormley Feb 18, 2026
1e2609c
fix: grey out disabled Calibration tab
clintongormley Feb 18, 2026
398f55a
refactor: rename motor_overhead to startup_delay, add endpoint_runon_…
clintongormley Feb 18, 2026
6bf1dc4
feat: add endpoint runon time field to Device tab after Pulse time
clintongormley Feb 18, 2026
e3066e0
refactor: move Endpoint Run-on Time field after entities, label above
clintongormley Feb 18, 2026
343f210
feat: default endpoint_runon_time to 2s
clintongormley Feb 18, 2026
3c43d83
refactor: horizontal label-left layout for input mode, pulse, runon, …
clintongormley Feb 18, 2026
7fcf898
Revert "refactor: horizontal label-left layout for input mode, pulse,…
clintongormley Feb 18, 2026
61e2ff9
fix: return default endpoint_runon_time (2s) from get_config API
clintongormley Feb 18, 2026
6229773
feat: add description text for endpoint run-on time field
clintongormley Feb 18, 2026
2f44e38
feat: show calibration hint per attribute, remove endpoint runon from…
clintongormley Feb 18, 2026
2044d6e
fix: show calibration hint immediately after position change
clintongormley Feb 18, 2026
5de72aa
refactor: rename config keys to match frontend naming convention
clintongormley Feb 18, 2026
292838d
test: update all tests for renamed config keys
clintongormley Feb 18, 2026
8617e48
docs: rewrite README for UI-first setup, deprecate YAML
clintongormley Feb 18, 2026
369c1b2
fix: use WS API for start_calibration, prevent wrapping own entities
clintongormley Feb 18, 2026
6591750
feat: raw_command WS API bypasses position tracker, remove auto-retur…
clintongormley Feb 18, 2026
eece9d5
fix: YAML input_mode ignored in devices_from_config, add factory and …
clintongormley Feb 18, 2026
e162e94
docs: add tilt strategy refactor design
clintongormley Feb 19, 2026
47c0928
docs: add tilt strategy refactor implementation plan
clintongormley Feb 19, 2026
0f00d09
feat: add TiltStrategy classes (sequential and proportional)
clintongormley Feb 19, 2026
59e37b2
refactor: replace tilt mode strings with TiltStrategy in cover_base, …
clintongormley Feb 19, 2026
6eab726
refactor: update websocket tilt_mode validation to new names
clintongormley Feb 19, 2026
f853602
test: update tilt_mode values to new names (proportional, sequential)
clintongormley Feb 19, 2026
f1213b8
feat: add v2→v3 config migration for tilt_mode rename
clintongormley Feb 19, 2026
633a980
test: fix direct cover construction to use tilt_strategy param
clintongormley Feb 19, 2026
483888a
refactor: split tilt_strategy.py into tilt_strategies package
clintongormley Feb 19, 2026
06f0cd5
refactor: simplify config migration by removing v2→v3 step
clintongormley Feb 19, 2026
8ef4cba
docs: add tilt strategies design doc
clintongormley Feb 19, 2026
0c3cae6
feat: add MovementStep dataclasses (TiltTo, TravelTo)
clintongormley Feb 19, 2026
ae73bdd
feat: add new command-based abstract methods to TiltStrategy ABC
clintongormley Feb 19, 2026
df1fe98
feat: implement command-based interface on ProportionalTilt
clintongormley Feb 19, 2026
ada5e3b
feat: redesign SequentialTilt with phased movement model
clintongormley Feb 19, 2026
9f318c6
style: apply ruff formatting to new tilt strategy code
clintongormley Feb 19, 2026
618fc57
fix: add explicit type annotations to satisfy pyright invariance checks
clintongormley Feb 19, 2026
e0bcceb
feat: add DualMotorTilt strategy with boundary lock and safety
clintongormley Feb 19, 2026
1bbd227
refactor: use strategy.name property for state attributes
clintongormley Feb 19, 2026
38b613e
feat: add dual_motor to strategy factory with config passthrough
clintongormley Feb 19, 2026
a45ca15
feat: add dual_motor config options to websocket API
clintongormley Feb 19, 2026
1ff43c9
feat: add CONF constants for dual-motor config options
clintongormley Feb 19, 2026
559cd36
refactor: remove old reactive abstract methods from TiltStrategy ABC
clintongormley Feb 19, 2026
5b1cad8
refactor: remove old reactive methods from tilt strategy subclasses
clintongormley Feb 19, 2026
7894847
refactor: migrate cover_base.py from old reactive to plan-based tilt …
clintongormley Feb 19, 2026
d6ffb46
docs: add tilt strategy config UI design
clintongormley Feb 19, 2026
039be0a
docs: add tilt config UI implementation plan
clintongormley Feb 19, 2026
7488e36
feat: add get_calibratable_attributes() filtered by tilt mode
clintongormley Feb 19, 2026
032c559
fix: dual-motor fields now round-trip through WebSocket API
clintongormley Feb 19, 2026
4673963
feat: update tilt mode dropdown to sequential/proportional/dual_motor
clintongormley Feb 19, 2026
39decad
feat: add Tilt Motor section with entity pickers and config fields
clintongormley Feb 19, 2026
eb59b9d
feat: strategy-aware position presets and calibration filtering
clintongormley Feb 19, 2026
cece343
feat: mode-specific calibration hints for all tilt strategies
clintongormley Feb 19, 2026
1f7221e
fix: tilt safety guards, auto-save, and dual motor controls
clintongormley Feb 20, 2026
ef43c47
docs: add inline tilt strategy design
clintongormley Feb 21, 2026
c86e065
docs: add inline tilt implementation plan
clintongormley Feb 21, 2026
351b3e1
feat: add restores_tilt property to TiltStrategy
clintongormley Feb 21, 2026
9f9f62a
feat: add InlineTilt strategy class
clintongormley Feb 21, 2026
9abcaef
feat: wire InlineTilt into factory, API, and frontend
clintongormley Feb 21, 2026
d0d19ab
feat: enable tilt restore for inline tilt via main motor
clintongormley Feb 21, 2026
855ec6a
test: add inline tilt tests for set_known_position, properties, and c…
clintongormley Feb 21, 2026
ba8f253
refactor: replace xknx TravelCalculator with HA-convention copy
clintongormley Feb 21, 2026
94d1e5a
fix: tilt snap behavior for inline and dual motor at endpoints
clintongormley Feb 21, 2026
cb680a7
fix: show tilt options in calibration and position reset for inline mode
clintongormley Feb 21, 2026
bd4686b
fix: abandon active lifecycle when new movement command arrives
clintongormley Feb 21, 2026
f014d8e
fix: use 3 overhead steps for tilt delay calibration
clintongormley Feb 21, 2026
43dd02c
docs: add state monitoring design document
clintongormley Feb 13, 2026
11e4bbf
docs: add state monitoring implementation plan
clintongormley Feb 13, 2026
ca60d20
feat(state-monitoring): add imports, instance variables, and listener…
clintongormley Feb 13, 2026
66f3a0b
feat(state-monitoring): add echo filtering, external flag, and state …
clintongormley Feb 13, 2026
a3648ce
test: increase coverage from 66% to 99% (126 new tests)
clintongormley Feb 17, 2026
30293ee
style: fix formatting and remove unused imports
clintongormley Feb 17, 2026
ba70e85
chore: add .coverage to .gitignore and remove from tracking
clintongormley Feb 17, 2026
a40a11e
feat: state monitoring for wrapped cover entities
clintongormley Feb 18, 2026
9596633
chore: remove test_config_flow.py for unimplemented options flow
clintongormley Feb 18, 2026
830c563
feat: mode-specific external state monitoring with tilt switch support
clintongormley Feb 21, 2026
e5f30de
fix: update calibration hints and dual_motor defaults in frontend card
clintongormley Feb 21, 2026
48b8a88
feat: make all frontend card strings translatable
clintongormley Feb 21, 2026
932982f
docs: update README and CHANGELOG for state monitoring and tilt features
clintongormley Feb 21, 2026
f38d95b
chore: remove branch-only migrations, compat shims, and dead code
clintongormley Feb 21, 2026
409913b
fix: resolve all pyright type errors across codebase
clintongormley Feb 21, 2026
442d490
chore: sync pl and pt translations with strings.json
clintongormley Feb 21, 2026
bdc3c0d
test: improve coverage from 87% to 97% (+75 tests)
clintongormley Feb 21, 2026
affe58d
docs: add inline tilt, xknx removal, and test coverage to CHANGELOG
clintongormley Feb 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
pip install homeassistant
pip install pytest pytest-asyncio pytest-homeassistant-custom-component

- name: Run tests
run: pytest tests/ -v

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Install ruff
run: pip install ruff

- name: Check formatting
run: ruff format --check .

- name: Check linting
run: ruff check .
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

__pycache__/

*.iml
*.iml.coverage
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
## Unreleased

### Features

- **External state monitoring:** Detects physical switch presses and keeps the position tracker in sync with actual motor state. Supports all input modes (switch, pulse, toggle) for both cover and tilt switches.
- **Separate tilt motor (dual_motor) mode:** Support for covers with a dedicated tilt motor, with configurable safe tilt position and max tilt allowed position.
- **Inline tilt mode:** Support for covers where the tilt mechanism is part of the main travel range (e.g. Venetian blinds), with configurable tilt range within the overall travel.
- **Tilt switch monitoring:** Monitors tilt switch entities for external changes in all modes (pulse, switch, toggle).
- **Translatable frontend card:** All card strings externalized to `strings.json` with `_t()` helper. Translations for English, Portuguese, and Polish included.
- **Toggle mode improvements:** Debounce for momentary switches, cross-direction external toggles treated as stop, HA UI direction changes still reverse.

### Improvements

- Replaced external `xknx` TravelCalculator dependency with a local HA-convention copy (no external dependencies)
- Updated calibration hint descriptions for all tilt modes (inline, sequential, dual_motor)
- Frontend defaults safe_tilt_position=100 and max_tilt_allowed_position=0 for new dual_motor configs
- Cleaned up diagnostic logging in toggle mode
- Improved test coverage from 66% to 97% (478 tests)

### Bug Fixes

- Fixed pulse mode stop switch not stopping the position tracker
- Fixed toggle mode external state handler only reacting to ON→OFF (now handles both transitions)
- Fixed switch mode tilt handler using pulse-mode behavior (now uses latching: ON=start, OFF=stop)
- Fixed tilt switch echo filtering (pending=2 for direction switches to handle ON+OFF transitions)
- Fixed wrapped cover handler not tracking direction changes (opening→closing)

## 3.0.0 (2025-12-10)

### Features
Expand Down
301 changes: 169 additions & 132 deletions README.md

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions custom_components/cover_time_based/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Cover Time Based integration."""

import logging
from pathlib import Path

from homeassistant.components import frontend
from homeassistant.components.http import StaticPathConfig
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant

from .websocket_api import async_register_websocket_api

_LOGGER = logging.getLogger(__name__)

DOMAIN = "cover_time_based"
PLATFORMS: list[Platform] = [Platform.COVER]
_FRONTEND_KEY = f"{DOMAIN}_frontend_registered"


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Cover Time Based from a config entry."""
# Register frontend and WebSocket API once (not per entry).
# Done before platform setup so the card works even if the entity fails.
if _FRONTEND_KEY not in hass.data:
hass.data[_FRONTEND_KEY] = True

async_register_websocket_api(hass)

if hass.http is not None:
await hass.http.async_register_static_paths(
[
StaticPathConfig(
"/cover_time_based_panel",
str(Path(__file__).parent / "frontend"),
cache_headers=False,
)
]
)

frontend.add_extra_js_url(
hass, "/cover_time_based_panel/cover-time-based-card.js"
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_update_options))

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)


async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update - reload the entry."""
await hass.config_entries.async_reload(entry.entry_id)
43 changes: 43 additions & 0 deletions custom_components/cover_time_based/calibration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Calibration support for cover_time_based."""

from __future__ import annotations

import time
from asyncio import Task
from dataclasses import dataclass, field

CALIBRATION_STEP_PAUSE = 2.0
CALIBRATION_OVERHEAD_STEPS = 8
CALIBRATION_TILT_OVERHEAD_STEPS = 3
CALIBRATION_MIN_MOVEMENT_START = 0.1
CALIBRATION_MIN_MOVEMENT_INCREMENT = 0.1
CALIBRATION_MIN_MOVEMENT_INITIAL_PAUSE = 3.0

CALIBRATABLE_ATTRIBUTES = [
"travel_time_close",
"travel_time_open",
"travel_startup_delay",
"tilt_time_close",
"tilt_time_open",
"tilt_startup_delay",
"min_movement_time",
]

SERVICE_START_CALIBRATION = "start_calibration"
SERVICE_STOP_CALIBRATION = "stop_calibration"


@dataclass
class CalibrationState:
"""Holds state for an in-progress calibration test."""

attribute: str
timeout: float
started_at: float = field(default_factory=time.monotonic)
step_count: int = 0
step_duration: float | None = None
last_pulse_duration: float | None = None
continuous_start: float | None = None
move_command: str | None = None
timeout_task: Task | None = field(default=None, repr=False)
automation_task: Task | None = field(default=None, repr=False)
40 changes: 40 additions & 0 deletions custom_components/cover_time_based/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Config flow for Cover Time Based integration."""

from __future__ import annotations

from typing import Any

import voluptuous as vol
from homeassistant.config_entries import (
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.const import CONF_NAME
from homeassistant.helpers.selector import TextSelector

from .cover import DOMAIN


class CoverTimeBasedConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Cover Time Based."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Create a new time-based cover helper."""
if user_input is not None:
return self.async_create_entry(
title=user_input[CONF_NAME],
data={},
options={},
)

schema = vol.Schema(
{
vol.Required(CONF_NAME): TextSelector(),
}
)

return self.async_show_form(step_id="user", data_schema=schema)
Loading
Loading