mirror of
https://github.com/home-assistant/core.git
synced 2026-09-27 18:08:37 -04:00
939 lines
36 KiB
Python
939 lines
36 KiB
Python
"""Support for Overkiz covers - shutters etc."""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, override
|
|
|
|
from pyoverkiz.enums import (
|
|
OverkizCommand,
|
|
OverkizCommandParam,
|
|
OverkizState,
|
|
UIClass,
|
|
UIWidget,
|
|
)
|
|
from pyoverkiz.types import StateType as OverkizStateType
|
|
|
|
from homeassistant.components.cover import (
|
|
ATTR_POSITION,
|
|
ATTR_TILT_POSITION,
|
|
CoverDeviceClass,
|
|
CoverEntity,
|
|
CoverEntityDescription,
|
|
CoverEntityFeature,
|
|
)
|
|
from homeassistant.const import Platform
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
|
|
from . import OverkizDataConfigEntry
|
|
from .const import DOMAIN, LOGGER
|
|
from .coordinator import OverkizDataUpdateCoordinator
|
|
from .entity import OverkizDescriptiveEntity
|
|
|
|
# Special position values reported by some Overkiz devices
|
|
_POSITION_MY = 108 # "My position" preset
|
|
_POSITION_UNKNOWN = 124 # "Unknown position" preset
|
|
|
|
# Default tilt step size (in degrees) used for RTS tilt commands
|
|
_TILT_STEP_SIZE = 5
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class OverkizCoverDescription(CoverEntityDescription):
|
|
"""Class to describe an Overkiz cover."""
|
|
|
|
open_command: OverkizCommand | None = None
|
|
close_command: OverkizCommand | None = None
|
|
stop_command: OverkizCommand | None = None
|
|
current_position_state: OverkizState | None = None
|
|
invert_position: bool = True
|
|
set_position_command: OverkizCommand | None = None
|
|
is_closed_state: OverkizState | None = None
|
|
current_tilt_position_state: OverkizState | None = None
|
|
invert_tilt_position: bool = True
|
|
set_tilt_position_command: OverkizCommand | None = None
|
|
open_tilt_command: OverkizCommand | None = None
|
|
open_tilt_command_args: tuple[OverkizStateType, ...] = ()
|
|
close_tilt_command: OverkizCommand | None = None
|
|
close_tilt_command_args: tuple[OverkizStateType, ...] = ()
|
|
stop_tilt_command: OverkizCommand | None = None
|
|
|
|
|
|
COVER_DESCRIPTIONS: list[OverkizCoverDescription] = [
|
|
##
|
|
## Overrides via UIWidget
|
|
##
|
|
# Needs override to support position (and remove support for
|
|
# tilt position which is not supported by this device)
|
|
# uiClass is Pergola
|
|
OverkizCoverDescription(
|
|
key=UIWidget.PERGOLA_HORIZONTAL_AWNING,
|
|
device_class=CoverDeviceClass.AWNING,
|
|
current_position_state=OverkizState.CORE_DEPLOYMENT,
|
|
set_position_command=OverkizCommand.SET_DEPLOYMENT,
|
|
open_command=OverkizCommand.DEPLOY,
|
|
close_command=OverkizCommand.UNDEPLOY,
|
|
stop_command=OverkizCommand.STOP,
|
|
invert_position=False,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIWidget.PERGOLA_HORIZONTAL_AWNING_UNO,
|
|
device_class=CoverDeviceClass.AWNING,
|
|
current_position_state=OverkizState.CORE_DEPLOYMENT,
|
|
set_position_command=OverkizCommand.SET_DEPLOYMENT,
|
|
open_command=OverkizCommand.DEPLOY,
|
|
close_command=OverkizCommand.UNDEPLOY,
|
|
stop_command=OverkizCommand.STOP,
|
|
invert_position=False,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
),
|
|
# Needs override to omit is_closed_state, since OpenClosedState is unreliable
|
|
# uiClass is RollerShutter
|
|
OverkizCoverDescription(
|
|
key=UIWidget.POSITIONABLE_ROLLER_SHUTTER_UNO,
|
|
device_class=CoverDeviceClass.SHUTTER,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override to support lower/upper position control
|
|
# uiClass is RollerShutter
|
|
OverkizCoverDescription(
|
|
key=UIWidget.POSITIONABLE_DUAL_ROLLER_SHUTTER,
|
|
device_class=CoverDeviceClass.SHUTTER,
|
|
current_position_state=OverkizState.CORE_UPPER_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_UPPER_CLOSURE,
|
|
open_command=OverkizCommand.UPPER_OPEN,
|
|
close_command=OverkizCommand.UPPER_CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
is_closed_state=OverkizState.CORE_UPPER_OPEN_CLOSED,
|
|
# Lower position used as tilt (no separate tilt state)
|
|
current_tilt_position_state=OverkizState.CORE_LOWER_CLOSURE,
|
|
set_tilt_position_command=OverkizCommand.SET_LOWER_CLOSURE,
|
|
open_tilt_command=OverkizCommand.LOWER_OPEN,
|
|
close_tilt_command=OverkizCommand.LOWER_CLOSE,
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override to add support for very specific tilt commands
|
|
# uiClass is VenetianBlind
|
|
OverkizCoverDescription(
|
|
key=UIWidget.TILT_ONLY_VENETIAN_BLIND,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
# Position commands fully open/close the tilt
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
# position (1-127), execution duration (0-15, optional)
|
|
open_tilt_command=OverkizCommand.TILT_POSITIVE,
|
|
open_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
|
|
close_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override to support very specific tilt commands
|
|
# (rts:ExteriorVenetianBlindRTSComponent)
|
|
# uiClass is ExteriorVenetianBlind
|
|
OverkizCoverDescription(
|
|
key=UIWidget.UP_DOWN_EXTERIOR_VENETIAN_BLIND,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
# position (1-127), execution duration (0-15, optional)
|
|
open_tilt_command=OverkizCommand.TILT_POSITIVE,
|
|
open_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
|
|
close_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override to support very specific tilt commands
|
|
# (rts:VenetianBlindRTSComponent)
|
|
# uiClass is VenetianBlind
|
|
OverkizCoverDescription(
|
|
key=UIWidget.UP_DOWN_VENETIAN_BLIND,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
# position (1-127), execution duration (0-15, optional)
|
|
open_tilt_command=OverkizCommand.TILT_POSITIVE,
|
|
open_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
|
|
close_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override to support very specific tilt commands (rts:SheerBlindRTSComponent)
|
|
# uiClass is VenetianBlind
|
|
OverkizCoverDescription(
|
|
key=UIWidget.UP_DOWN_SHEER_SCREEN,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
# position (1-127), execution duration (0-15, optional)
|
|
open_tilt_command=OverkizCommand.TILT_POSITIVE,
|
|
open_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
|
|
close_tilt_command_args=(_TILT_STEP_SIZE,),
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override since BioclimaticPergola uses core:SlatsOpenClosedState
|
|
# and core:SlateOrientationState (tilt-only, no position)
|
|
# uiClass is Pergola
|
|
OverkizCoverDescription(
|
|
key=UIWidget.BIOCLIMATIC_PERGOLA,
|
|
device_class=CoverDeviceClass.AWNING,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
is_closed_state=OverkizState.CORE_SLATS_OPEN_CLOSED,
|
|
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
|
|
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
|
|
open_tilt_command=OverkizCommand.OPEN_SLATS,
|
|
close_tilt_command=OverkizCommand.CLOSE_SLATS,
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override since PositionableGarageDoor reports
|
|
# core:OpenClosedUnknownState instead of core:OpenClosedState
|
|
# uiClass is GarageDoor
|
|
OverkizCoverDescription(
|
|
key=UIWidget.POSITIONABLE_GARAGE_DOOR,
|
|
device_class=CoverDeviceClass.GARAGE,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_UNKNOWN,
|
|
),
|
|
# Needs override since DiscretePositionableGarageDoor reports
|
|
# core:OpenClosedUnknownState instead of core:OpenClosedState
|
|
# uiClass is GarageDoor
|
|
OverkizCoverDescription(
|
|
key=UIWidget.DISCRETE_POSITIONABLE_GARAGE_DOOR,
|
|
device_class=CoverDeviceClass.GARAGE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_UNKNOWN,
|
|
),
|
|
# Needs override since PositionableGarageDoorWithPartialPosition reports
|
|
# core:OpenClosedPartialState instead of core:OpenClosedState
|
|
# uiClass is GarageDoor
|
|
OverkizCoverDescription(
|
|
key=UIWidget.POSITIONABLE_GARAGE_DOOR_WITH_PARTIAL_POSITION,
|
|
device_class=CoverDeviceClass.GARAGE,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PARTIAL,
|
|
),
|
|
# Needs override since DiscreteGateWithPedestrianPosition reports
|
|
# core:OpenClosedPedestrianState instead of core:OpenClosedState
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.DISCRETE_GATE_WITH_PEDESTRIAN_POSITION,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override since OpenCloseGate4T only supports the cycle command
|
|
# (rts:GateOpenerRTS4TComponent)
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.OPEN_CLOSE_GATE_4T,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.CYCLE,
|
|
close_command=OverkizCommand.CYCLE,
|
|
),
|
|
# Needs override since UpDownGarageDoor4T only supports the cycle command
|
|
# (rts:GarageDoor4TRTSComponent)
|
|
# uiClass is GarageDoor
|
|
OverkizCoverDescription(
|
|
key=UIWidget.UP_DOWN_GARAGE_DOOR_4T,
|
|
device_class=CoverDeviceClass.GARAGE,
|
|
open_command=OverkizCommand.CYCLE,
|
|
close_command=OverkizCommand.CYCLE,
|
|
),
|
|
# Needs override since OpenCloseSlidingGarageDoor4T only supports the cycle command
|
|
# uiClass is GarageDoor
|
|
OverkizCoverDescription(
|
|
key=UIWidget.OPEN_CLOSE_SLIDING_GARAGE_DOOR_4T,
|
|
device_class=CoverDeviceClass.GARAGE,
|
|
open_command=OverkizCommand.CYCLE,
|
|
close_command=OverkizCommand.CYCLE,
|
|
),
|
|
# Needs override since OpenCloseSlidingGate4T only supports the cycle command
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.OPEN_CLOSE_SLIDING_GATE_4T,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.CYCLE,
|
|
close_command=OverkizCommand.CYCLE,
|
|
),
|
|
# Needs override since CyclicGarageDoor only supports the cycle command
|
|
# (io:CyclicGarageOpenerIOComponent)
|
|
# uiClass is GarageDoor
|
|
OverkizCoverDescription(
|
|
key=UIWidget.CYCLIC_GARAGE_DOOR,
|
|
device_class=CoverDeviceClass.GARAGE,
|
|
open_command=OverkizCommand.CYCLE,
|
|
close_command=OverkizCommand.CYCLE,
|
|
),
|
|
# Needs override since CyclicSlidingGateOpener only supports the cycle command
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.CYCLIC_SLIDING_GATE_OPENER,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.CYCLE,
|
|
close_command=OverkizCommand.CYCLE,
|
|
),
|
|
# Needs override since CyclicSwingingGateOpener only supports the cycle command
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.CYCLIC_SWINGING_GATE_OPENER,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.CYCLE,
|
|
close_command=OverkizCommand.CYCLE,
|
|
),
|
|
# Needs override since SlidingDiscreteGateWithPedestrianPosition reports
|
|
# core:OpenClosedPedestrianState instead of core:OpenClosedState
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.SLIDING_DISCRETE_GATE_WITH_PEDESTRIAN_POSITION,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override since OpenCloseGateWithPedestrianPosition reports
|
|
# core:OpenClosedPedestrianState instead of core:OpenClosedState
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.OPEN_CLOSE_GATE_WITH_PEDESTRIAN_POSITION,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override since OpenCloseSlidingGateWithPedestrianPosition reports
|
|
# core:OpenClosedPedestrianState instead of core:OpenClosedState
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.OPEN_CLOSE_SLIDING_GATE_WITH_PEDESTRIAN_POSITION,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override since PositionableGateWithPedestrianPosition reports
|
|
# core:OpenClosedPedestrianState instead of core:OpenClosedState
|
|
# uiClass is Gate
|
|
OverkizCoverDescription(
|
|
key=UIWidget.POSITIONABLE_GATE_WITH_PEDESTRIAN_POSITION,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
# Needs override to support this Generic device (rts:GenericRTSComponent)
|
|
# uiClass is Generic (not mapped to cover as this is a Generic device class)
|
|
OverkizCoverDescription(
|
|
key=UIWidget.RTS_GENERIC,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
##
|
|
## Default cover behavior (via UIClass)
|
|
##
|
|
OverkizCoverDescription(
|
|
key=UIClass.ADJUSTABLE_SLATS_ROLLER_SHUTTER,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
|
|
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.AWNING,
|
|
device_class=CoverDeviceClass.AWNING,
|
|
current_position_state=OverkizState.CORE_DEPLOYMENT,
|
|
set_position_command=OverkizCommand.SET_DEPLOYMENT,
|
|
open_command=OverkizCommand.DEPLOY,
|
|
close_command=OverkizCommand.UNDEPLOY,
|
|
invert_position=False,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.CURTAIN,
|
|
device_class=CoverDeviceClass.CURTAIN,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.EXTERIOR_SCREEN,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.EXTERIOR_VENETIAN_BLIND,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
|
|
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
|
|
open_tilt_command=OverkizCommand.TILT_DOWN,
|
|
close_tilt_command=OverkizCommand.TILT_UP,
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.GARAGE_DOOR,
|
|
device_class=CoverDeviceClass.GARAGE,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.GATE,
|
|
device_class=CoverDeviceClass.GATE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.PERGOLA,
|
|
device_class=CoverDeviceClass.AWNING,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
|
|
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.ROLLER_SHUTTER,
|
|
device_class=CoverDeviceClass.SHUTTER,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
|
|
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.SCREEN,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.SHUTTER,
|
|
device_class=CoverDeviceClass.SHUTTER,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.SWINGING_SHUTTER,
|
|
device_class=CoverDeviceClass.SHUTTER,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.VENETIAN_BLIND,
|
|
device_class=CoverDeviceClass.BLIND,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
stop_command=OverkizCommand.STOP,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
|
|
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
|
|
open_tilt_command=OverkizCommand.TILT_UP,
|
|
close_tilt_command=OverkizCommand.TILT_DOWN,
|
|
stop_tilt_command=OverkizCommand.STOP,
|
|
),
|
|
OverkizCoverDescription(
|
|
key=UIClass.WINDOW,
|
|
device_class=CoverDeviceClass.WINDOW,
|
|
current_position_state=OverkizState.CORE_CLOSURE,
|
|
set_position_command=OverkizCommand.SET_CLOSURE,
|
|
open_command=OverkizCommand.OPEN,
|
|
close_command=OverkizCommand.CLOSE,
|
|
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
|
stop_command=OverkizCommand.STOP,
|
|
),
|
|
]
|
|
|
|
SUPPORTED_DEVICES = {description.key: description for description in COVER_DESCRIPTIONS}
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: OverkizDataConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the Overkiz covers from a config entry."""
|
|
data = entry.runtime_data
|
|
|
|
entities: list[OverkizCover] = []
|
|
|
|
for device in data.platforms[Platform.COVER]:
|
|
if description := (
|
|
SUPPORTED_DEVICES.get(device.widget)
|
|
or SUPPORTED_DEVICES.get(device.ui_class)
|
|
):
|
|
entities.append(
|
|
OverkizCover(device.device_url, data.coordinator, description)
|
|
)
|
|
|
|
# Cover platform does not support configuring the speed of the cover
|
|
# For covers where the speed can be configured, we create a separate entity
|
|
if (
|
|
OverkizCommand.SET_CLOSURE_AND_LINEAR_SPEED
|
|
in device.definition.commands
|
|
):
|
|
entities.append(
|
|
OverkizLowSpeedCover(
|
|
device.device_url, data.coordinator, description
|
|
)
|
|
)
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
class OverkizCover(OverkizDescriptiveEntity, CoverEntity):
|
|
"""Representation of an Overkiz Cover."""
|
|
|
|
entity_description: OverkizCoverDescription
|
|
|
|
def __init__(
|
|
self,
|
|
device_url: str,
|
|
coordinator: OverkizDataUpdateCoordinator,
|
|
description: OverkizCoverDescription,
|
|
) -> None:
|
|
"""Initialize the device."""
|
|
super().__init__(device_url, coordinator, description)
|
|
|
|
# Use device url as unique ID for backwards compatibility
|
|
self._attr_unique_id = self.device.device_url
|
|
|
|
# Overkiz does support covers where only tilt commands are supported
|
|
# and HA sets by default open/close as supported feature which conflicts
|
|
supported_features = CoverEntityFeature(0)
|
|
|
|
if self.entity_description.open_command and self.device.supports_command(
|
|
self.entity_description.open_command
|
|
):
|
|
supported_features |= CoverEntityFeature.OPEN
|
|
|
|
if self.entity_description.stop_command and self.device.supports_command(
|
|
self.entity_description.stop_command
|
|
):
|
|
supported_features |= CoverEntityFeature.STOP
|
|
|
|
if self.entity_description.close_command and self.device.supports_command(
|
|
self.entity_description.close_command
|
|
):
|
|
supported_features |= CoverEntityFeature.CLOSE
|
|
|
|
if self.entity_description.open_tilt_command and self.device.supports_command(
|
|
self.entity_description.open_tilt_command
|
|
):
|
|
supported_features |= CoverEntityFeature.OPEN_TILT
|
|
|
|
if (
|
|
self.entity_description.stop_tilt_command
|
|
and self.device.supports_command(
|
|
self.entity_description.stop_tilt_command
|
|
)
|
|
):
|
|
supported_features |= CoverEntityFeature.STOP_TILT
|
|
|
|
if self.entity_description.close_tilt_command and self.device.supports_command(
|
|
self.entity_description.close_tilt_command
|
|
):
|
|
supported_features |= CoverEntityFeature.CLOSE_TILT
|
|
|
|
if (
|
|
self.entity_description.set_tilt_position_command
|
|
and self.device.supports_command(
|
|
self.entity_description.set_tilt_position_command
|
|
)
|
|
):
|
|
supported_features |= CoverEntityFeature.SET_TILT_POSITION
|
|
|
|
if (
|
|
self.entity_description.set_position_command
|
|
and self.device.supports_command(
|
|
self.entity_description.set_position_command
|
|
)
|
|
):
|
|
supported_features |= CoverEntityFeature.SET_POSITION
|
|
|
|
self._attr_supported_features = supported_features
|
|
|
|
@property
|
|
@override
|
|
def is_closed(self) -> bool | None:
|
|
"""Return if the cover is closed."""
|
|
if is_closed_state := self.entity_description.is_closed_state:
|
|
if state := self.device.states.get(is_closed_state):
|
|
if state.value == OverkizCommandParam.UNKNOWN:
|
|
return None
|
|
return state.value == OverkizCommandParam.CLOSED
|
|
|
|
if (position := self.current_cover_position) is not None:
|
|
return position == 0
|
|
|
|
if (tilt_position := self.current_cover_tilt_position) is not None:
|
|
return tilt_position == 0
|
|
|
|
return None
|
|
|
|
@property
|
|
@override
|
|
def current_cover_position(self) -> int | None:
|
|
"""Return current position of cover.
|
|
|
|
None is unknown, 0 is closed, 100 is fully open.
|
|
"""
|
|
state_name = self.entity_description.current_position_state
|
|
|
|
if not state_name or not (state := self.device.states.get(state_name)):
|
|
return None
|
|
|
|
position = state.value_as_int
|
|
|
|
# Fallback for "My position" preset
|
|
if position == _POSITION_MY:
|
|
LOGGER.debug(
|
|
"Overkiz cover position is invalid (%s). Device: %s, State: %s",
|
|
_POSITION_MY,
|
|
self.device.device_url,
|
|
state_name,
|
|
)
|
|
|
|
if fallback_state := self.device.states.get(
|
|
OverkizState.CORE_MEMORIZED_1_POSITION
|
|
):
|
|
position = fallback_state.value_as_int
|
|
else:
|
|
return None
|
|
|
|
# Fallback for "Unknown position" preset
|
|
if position == _POSITION_UNKNOWN:
|
|
LOGGER.debug(
|
|
"Overkiz cover position is invalid (%s). Device: %s, State: %s",
|
|
_POSITION_UNKNOWN,
|
|
self.device.device_url,
|
|
state_name,
|
|
)
|
|
|
|
if fallback_state := self.device.states.get(
|
|
OverkizState.CORE_TARGET_CLOSURE
|
|
):
|
|
position = fallback_state.value_as_int
|
|
else:
|
|
return None
|
|
|
|
if position is None:
|
|
return None
|
|
|
|
# Invert position if needed (some devices report 0 as open and 100 as closed)
|
|
if self.entity_description.invert_position:
|
|
position = 100 - position
|
|
|
|
return position
|
|
|
|
@override
|
|
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
|
"""Move the cover to a specific position."""
|
|
position = kwargs[ATTR_POSITION]
|
|
if self.entity_description.invert_position:
|
|
position = 100 - position
|
|
|
|
if command := self.entity_description.set_position_command:
|
|
await self.executor.async_execute_command(command, position)
|
|
|
|
@override
|
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
|
"""Open the cover."""
|
|
if command := self.entity_description.open_command:
|
|
await self.executor.async_execute_command(command)
|
|
|
|
@override
|
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
|
"""Close the cover."""
|
|
if command := self.entity_description.close_command:
|
|
await self.executor.async_execute_command(command)
|
|
|
|
@override
|
|
async def async_stop_cover(self, **kwargs: Any) -> None:
|
|
"""Stop the cover."""
|
|
if command := self.entity_description.stop_command:
|
|
await self.executor.async_execute_command(command)
|
|
|
|
@property
|
|
@override
|
|
def current_cover_tilt_position(self) -> int | None:
|
|
"""Return current position of cover tilt.
|
|
|
|
None is unknown, 0 is closed, 100 is fully open.
|
|
"""
|
|
state_name = self.entity_description.current_tilt_position_state
|
|
|
|
if state_name and (state := self.device.states.get(state_name)):
|
|
position = state.value_as_int
|
|
if position is None:
|
|
return None
|
|
|
|
if self.entity_description.invert_tilt_position:
|
|
position = 100 - position
|
|
|
|
return position
|
|
|
|
return None
|
|
|
|
@override
|
|
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
|
"""Move the cover tilt to a specific position."""
|
|
position = kwargs[ATTR_TILT_POSITION]
|
|
|
|
if self.entity_description.invert_tilt_position:
|
|
position = 100 - position
|
|
|
|
if command := self.entity_description.set_tilt_position_command:
|
|
await self.executor.async_execute_command(command, position)
|
|
|
|
async def async_set_cover_position_and_tilt(self, **kwargs: Any) -> None:
|
|
"""Move cover and tilt to a specific position.
|
|
|
|
Exposed as the `overkiz.set_cover_position_and_tilt`
|
|
service action. Uses the setClosureAndOrientation
|
|
command to move slats and closure in a single
|
|
instruction. Calling set_cover_position and
|
|
set_cover_tilt_position sequentially will cause the
|
|
motor to stop between commands on some devices (e.g.
|
|
Somfy DynamicExteriorVenetianBlind).
|
|
"""
|
|
if not self.device.supports_command(OverkizCommand.SET_CLOSURE_AND_ORIENTATION):
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="unsupported_set_position_and_tilt",
|
|
)
|
|
|
|
position = kwargs[ATTR_POSITION]
|
|
tilt_position = kwargs[ATTR_TILT_POSITION]
|
|
|
|
if self.entity_description.invert_position:
|
|
position = 100 - position
|
|
if self.entity_description.invert_tilt_position:
|
|
tilt_position = 100 - tilt_position
|
|
|
|
await self.executor.async_execute_command(
|
|
OverkizCommand.SET_CLOSURE_AND_ORIENTATION,
|
|
position,
|
|
tilt_position,
|
|
)
|
|
|
|
@override
|
|
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
|
"""Open the cover tilt."""
|
|
if command := self.entity_description.open_tilt_command:
|
|
await self.executor.async_execute_command(
|
|
command, *self.entity_description.open_tilt_command_args
|
|
)
|
|
|
|
@override
|
|
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
|
"""Close the cover tilt."""
|
|
if command := self.entity_description.close_tilt_command:
|
|
await self.executor.async_execute_command(
|
|
command, *self.entity_description.close_tilt_command_args
|
|
)
|
|
|
|
@override
|
|
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
|
|
"""Stop the cover tilt."""
|
|
if command := self.entity_description.stop_tilt_command:
|
|
await self.executor.async_execute_command(command)
|
|
|
|
@property
|
|
@override
|
|
def is_opening(self) -> bool | None:
|
|
"""Return if the cover is opening or not."""
|
|
# Check if any open() commands are currently running for this device
|
|
if (command := self.entity_description.open_command) and self.is_running(
|
|
command
|
|
):
|
|
return True
|
|
|
|
# Check if any open_tilt() commands are currently running for this device
|
|
if (command := self.entity_description.open_tilt_command) and self.is_running(
|
|
command
|
|
):
|
|
return True
|
|
|
|
if self.moving_offset is None:
|
|
return None
|
|
|
|
# Check if the cover is moving in a direction consistent with opening
|
|
if self.entity_description.invert_position:
|
|
return self.moving_offset > 0
|
|
return self.moving_offset < 0
|
|
|
|
@property
|
|
@override
|
|
def is_closing(self) -> bool | None:
|
|
"""Return if the cover is closing or not."""
|
|
# Check if any close() commands are currently running for this device
|
|
if (command := self.entity_description.close_command) and self.is_running(
|
|
command
|
|
):
|
|
return True
|
|
|
|
# Check if any close_tilt() commands are currently running for this device
|
|
if (command := self.entity_description.close_tilt_command) and self.is_running(
|
|
command
|
|
):
|
|
return True
|
|
|
|
if self.moving_offset is None:
|
|
return None
|
|
|
|
# Check if the cover is moving in a direction consistent with closing
|
|
if self.entity_description.invert_position:
|
|
return self.moving_offset < 0
|
|
return self.moving_offset > 0
|
|
|
|
def is_running(self, command: OverkizCommand) -> bool:
|
|
"""Return if the given commands are currently running."""
|
|
return any(
|
|
execution.get("device_url") == self.device.device_url
|
|
and execution.get("command_name") == command
|
|
for executions in self.coordinator.executions.values()
|
|
for execution in executions
|
|
)
|
|
|
|
@property
|
|
def moving_offset(self) -> int | None:
|
|
"""Return the offset between targeted and current position."""
|
|
moving_state = self.device.states.get(OverkizState.CORE_MOVING)
|
|
if moving_state is None or moving_state.value_as_bool is not True:
|
|
return None
|
|
|
|
current_closure = self.device.states.get(
|
|
self.entity_description.current_position_state or OverkizState.CORE_CLOSURE
|
|
)
|
|
target_closure = self.device.states.get(OverkizState.CORE_TARGET_CLOSURE)
|
|
|
|
if not current_closure or not target_closure:
|
|
return None
|
|
|
|
current_value = current_closure.value_as_int
|
|
target_value = target_closure.value_as_int
|
|
|
|
if current_value is None or target_value is None:
|
|
return None
|
|
|
|
if current_value in (_POSITION_MY, _POSITION_UNKNOWN):
|
|
return None
|
|
|
|
return current_value - target_value
|
|
|
|
|
|
class OverkizLowSpeedCover(OverkizCover):
|
|
"""Representation of an Overkiz Low Speed cover."""
|
|
|
|
def __init__(
|
|
self,
|
|
device_url: str,
|
|
coordinator: OverkizDataUpdateCoordinator,
|
|
description: OverkizCoverDescription,
|
|
) -> None:
|
|
"""Initialize the device."""
|
|
super().__init__(device_url, coordinator, description)
|
|
|
|
self._attr_name = "Low speed"
|
|
self._attr_unique_id = f"{self._attr_unique_id}_low_speed"
|
|
|
|
@override
|
|
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
|
"""Move the cover to a specific position."""
|
|
await self._async_set_cover_position_low_speed(kwargs[ATTR_POSITION])
|
|
|
|
@override
|
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
|
"""Open the cover."""
|
|
await self._async_set_cover_position_low_speed(100)
|
|
|
|
@override
|
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
|
"""Close the cover."""
|
|
await self._async_set_cover_position_low_speed(0)
|
|
|
|
async def _async_set_cover_position_low_speed(self, position: int) -> None:
|
|
"""Move the cover to a specific position with a low speed."""
|
|
await self.executor.async_execute_command(
|
|
OverkizCommand.SET_CLOSURE_AND_LINEAR_SPEED,
|
|
100 - position,
|
|
OverkizCommandParam.LOWSPEED,
|
|
)
|