feat: add tilt-on-closed cover entity for types 18 and 44
Some checks failed
Validate with hassfest / validate-hassfest (pull_request) Failing after 28s
Lint the code / Lint the code (pull_request) Successful in 12m0s

This commit is contained in:
Richard Mann
2026-04-11 17:57:17 +10:00
parent 354d06b468
commit deee1baad5
2 changed files with 34 additions and 2 deletions

View File

@@ -79,6 +79,7 @@ class ShadeCapability(NamedTuple):
has_tilt: bool = False has_tilt: bool = False
tilt_only: bool = False tilt_only: bool = False
is_tilt_on_closed: bool = False # tilt only available when fully closed
is_top_down: bool = False # position logic is inverted (SkyLift style) is_top_down: bool = False # position logic is inverted (SkyLift style)
is_tdbu: bool = False # dual-rail Top Down Bottom Up (needs two entities) is_tdbu: bool = False # dual-rail Top Down Bottom Up (needs two entities)
is_duolite: bool = False # dual-fabric sheer+opaque (needs three entities) is_duolite: bool = False # dual-fabric sheer+opaque (needs three entities)
@@ -95,8 +96,8 @@ SHADE_CAPABILITIES: Final[dict[int, ShadeCapability]] = {
39: ShadeCapability(has_tilt=True, tilt_only=True), 39: ShadeCapability(has_tilt=True, tilt_only=True),
40: ShadeCapability(has_tilt=True, tilt_only=True), 40: ShadeCapability(has_tilt=True, tilt_only=True),
# tilt on closed (tilt only available at fully closed position) # tilt on closed (tilt only available at fully closed position)
18: ShadeCapability(has_tilt=True), 18: ShadeCapability(has_tilt=True, is_tilt_on_closed=True),
44: ShadeCapability(has_tilt=True), 44: ShadeCapability(has_tilt=True, is_tilt_on_closed=True),
# top-down only (single rail, inverted position) # top-down only (single rail, inverted position)
7: ShadeCapability(is_top_down=True), 7: ShadeCapability(is_top_down=True),
10: ShadeCapability(is_top_down=True), 10: ShadeCapability(is_top_down=True),

View File

@@ -34,6 +34,8 @@ def _add_entities(
if caps.tilt_only: if caps.tilt_only:
entities: list[PowerViewCover] = [PowerViewCoverTiltOnly(coordinator)] entities: list[PowerViewCover] = [PowerViewCoverTiltOnly(coordinator)]
elif caps.is_tilt_on_closed:
entities = [PowerViewCoverTiltOnClosed(coordinator)]
elif caps.has_tilt: elif caps.has_tilt:
entities = [PowerViewCoverTilt(coordinator)] entities = [PowerViewCoverTilt(coordinator)]
elif caps.is_top_down: elif caps.is_top_down:
@@ -262,6 +264,35 @@ class PowerViewCoverTilt(PowerViewCover):
await self.async_set_cover_tilt_position(**_kwargs) await self.async_set_cover_tilt_position(**_kwargs)
class PowerViewCoverTiltOnClosed(PowerViewCoverTilt):
"""Representation of a PowerView shade whose tilt is only available when closed.
Examples: Bottom Up 90° (type 18), Twist (type 44).
If a tilt command arrives while the shade is open, the shade is closed first
so the tilt mechanism is engaged before the command is sent.
"""
def __init__(self, coordinator: PVCoordinator) -> None:
"""Initialize the shade."""
LOGGER.debug("%s: init() PowerViewCoverTiltOnClosed", coordinator.name)
super().__init__(coordinator)
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the tilt to a specific position, closing first if needed."""
if self.current_cover_position != CLOSED_POSITION:
LOGGER.debug("tilt-on-closed: closing shade before tilting")
try:
self._target_position = CLOSED_POSITION
await self._coord.api.close(velocity=self._coord.velocity)
self.async_write_ha_state()
except BleakError as err:
LOGGER.error("Failed to close cover '%s' before tilt: %s", self.name, err)
self._reset_target_position()
return
await super().async_set_cover_tilt_position(**kwargs)
class PowerViewCoverTopDown(PowerViewCover): class PowerViewCoverTopDown(PowerViewCover):
"""Representation of a top-down PowerView shade. """Representation of a top-down PowerView shade.