mirror of https://github.com/home-assistant/core
231 lines
8.3 KiB
Python
231 lines
8.3 KiB
Python
"""Cover entities for the Motionblinds Bluetooth integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import logging
|
|
from typing import Any
|
|
|
|
from motionblindsble.const import MotionBlindType, MotionRunningType
|
|
from motionblindsble.device import MotionDevice
|
|
|
|
from homeassistant.components.cover import (
|
|
ATTR_POSITION,
|
|
ATTR_TILT_POSITION,
|
|
CoverDeviceClass,
|
|
CoverEntity,
|
|
CoverEntityDescription,
|
|
CoverEntityFeature,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from .const import CONF_BLIND_TYPE, CONF_MAC_CODE, DOMAIN, ICON_VERTICAL_BLIND
|
|
from .entity import MotionblindsBLEEntity
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class MotionblindsBLECoverEntityDescription(CoverEntityDescription):
|
|
"""Entity description of a cover entity with default values."""
|
|
|
|
key: str = CoverDeviceClass.BLIND.value
|
|
translation_key: str = CoverDeviceClass.BLIND.value
|
|
|
|
|
|
SHADE_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription(
|
|
device_class=CoverDeviceClass.SHADE
|
|
)
|
|
BLIND_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription(
|
|
device_class=CoverDeviceClass.BLIND
|
|
)
|
|
CURTAIN_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription(
|
|
device_class=CoverDeviceClass.CURTAIN
|
|
)
|
|
VERTICAL_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription(
|
|
device_class=CoverDeviceClass.CURTAIN, icon=ICON_VERTICAL_BLIND
|
|
)
|
|
|
|
BLIND_TYPE_TO_ENTITY_DESCRIPTION: dict[str, MotionblindsBLECoverEntityDescription] = {
|
|
MotionBlindType.HONEYCOMB.name: SHADE_ENTITY_DESCRIPTION,
|
|
MotionBlindType.ROMAN.name: SHADE_ENTITY_DESCRIPTION,
|
|
MotionBlindType.ROLLER.name: SHADE_ENTITY_DESCRIPTION,
|
|
MotionBlindType.DOUBLE_ROLLER.name: SHADE_ENTITY_DESCRIPTION,
|
|
MotionBlindType.VENETIAN.name: BLIND_ENTITY_DESCRIPTION,
|
|
MotionBlindType.VENETIAN_TILT_ONLY.name: BLIND_ENTITY_DESCRIPTION,
|
|
MotionBlindType.CURTAIN.name: CURTAIN_ENTITY_DESCRIPTION,
|
|
MotionBlindType.VERTICAL.name: VERTICAL_ENTITY_DESCRIPTION,
|
|
}
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
|
) -> None:
|
|
"""Set up cover entity based on a config entry."""
|
|
|
|
cover_class: type[MotionblindsBLECoverEntity] = BLIND_TYPE_TO_CLASS[
|
|
entry.data[CONF_BLIND_TYPE].upper()
|
|
]
|
|
device: MotionDevice = hass.data[DOMAIN][entry.entry_id]
|
|
entity_description: MotionblindsBLECoverEntityDescription = (
|
|
BLIND_TYPE_TO_ENTITY_DESCRIPTION[entry.data[CONF_BLIND_TYPE].upper()]
|
|
)
|
|
entity: MotionblindsBLECoverEntity = cover_class(device, entry, entity_description)
|
|
|
|
async_add_entities([entity])
|
|
|
|
|
|
class MotionblindsBLECoverEntity(MotionblindsBLEEntity, CoverEntity):
|
|
"""Representation of a cover entity."""
|
|
|
|
_attr_is_closed: bool | None = None
|
|
_attr_name = None
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""Register device callbacks."""
|
|
_LOGGER.debug(
|
|
"(%s) Added %s cover entity (%s)",
|
|
self.entry.data[CONF_MAC_CODE],
|
|
MotionBlindType[self.entry.data[CONF_BLIND_TYPE].upper()].value.lower(),
|
|
BLIND_TYPE_TO_CLASS[self.entry.data[CONF_BLIND_TYPE].upper()].__name__,
|
|
)
|
|
self.device.register_running_callback(self.async_update_running)
|
|
self.device.register_position_callback(self.async_update_position)
|
|
|
|
async def async_stop_cover(self, **kwargs: Any) -> None:
|
|
"""Stop moving the cover entity."""
|
|
_LOGGER.debug("(%s) Stopping", self.entry.data[CONF_MAC_CODE])
|
|
await self.device.stop()
|
|
|
|
@callback
|
|
def async_update_running(
|
|
self, running_type: MotionRunningType | None, write_state: bool = True
|
|
) -> None:
|
|
"""Update the running type (e.g. opening/closing) of the cover entity."""
|
|
if running_type in {None, MotionRunningType.STILL, MotionRunningType.UNKNOWN}:
|
|
self._attr_is_opening = False
|
|
self._attr_is_closing = False
|
|
else:
|
|
self._attr_is_opening = running_type is MotionRunningType.OPENING
|
|
self._attr_is_closing = running_type is not MotionRunningType.OPENING
|
|
if running_type is not MotionRunningType.STILL:
|
|
self._attr_is_closed = None
|
|
if write_state:
|
|
self.async_write_ha_state()
|
|
|
|
@callback
|
|
def async_update_position(
|
|
self,
|
|
position: int | None,
|
|
tilt: int | None,
|
|
) -> None:
|
|
"""Update the position of the cover entity."""
|
|
if position is None:
|
|
self._attr_current_cover_position = None
|
|
self._attr_is_closed = None
|
|
else:
|
|
self._attr_current_cover_position = 100 - position
|
|
self._attr_is_closed = self._attr_current_cover_position == 0
|
|
if tilt is None:
|
|
self._attr_current_cover_tilt_position = None
|
|
else:
|
|
self._attr_current_cover_tilt_position = 100 - round(100 * tilt / 180)
|
|
self.async_write_ha_state()
|
|
|
|
|
|
class PositionCover(MotionblindsBLECoverEntity):
|
|
"""Representation of a cover entity with position capability."""
|
|
|
|
_attr_supported_features = (
|
|
CoverEntityFeature.OPEN
|
|
| CoverEntityFeature.CLOSE
|
|
| CoverEntityFeature.STOP
|
|
| CoverEntityFeature.SET_POSITION
|
|
)
|
|
|
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
|
"""Open the cover entity."""
|
|
_LOGGER.debug("(%s) Opening", self.entry.data[CONF_MAC_CODE])
|
|
await self.device.open()
|
|
|
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
|
"""Close the cover entity."""
|
|
_LOGGER.debug("(%s) Closing", self.entry.data[CONF_MAC_CODE])
|
|
await self.device.close()
|
|
|
|
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
|
"""Move the cover entity to a specific position."""
|
|
new_position: int = 100 - int(kwargs[ATTR_POSITION])
|
|
|
|
_LOGGER.debug(
|
|
"(%s) Setting position to %i",
|
|
self.entry.data[CONF_MAC_CODE],
|
|
new_position,
|
|
)
|
|
await self.device.position(new_position)
|
|
|
|
|
|
class TiltCover(MotionblindsBLECoverEntity):
|
|
"""Representation of a cover entity with tilt capability."""
|
|
|
|
_attr_supported_features = (
|
|
CoverEntityFeature.OPEN_TILT
|
|
| CoverEntityFeature.CLOSE_TILT
|
|
| CoverEntityFeature.STOP_TILT
|
|
| CoverEntityFeature.SET_TILT_POSITION
|
|
)
|
|
|
|
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
|
"""Tilt the cover entity open."""
|
|
_LOGGER.debug("(%s) Tilt opening", self.entry.data[CONF_MAC_CODE])
|
|
await self.device.open_tilt()
|
|
|
|
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
|
"""Tilt the cover entity closed."""
|
|
_LOGGER.debug("(%s) Tilt closing", self.entry.data[CONF_MAC_CODE])
|
|
await self.device.close_tilt()
|
|
|
|
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
|
|
"""Stop tilting the cover entity."""
|
|
await self.async_stop_cover(**kwargs)
|
|
|
|
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
|
"""Tilt the cover entity to a specific position."""
|
|
new_tilt: int = 100 - int(kwargs[ATTR_TILT_POSITION])
|
|
|
|
_LOGGER.debug(
|
|
"(%s) Setting tilt position to %i",
|
|
self.entry.data[CONF_MAC_CODE],
|
|
new_tilt,
|
|
)
|
|
await self.device.tilt(round(180 * new_tilt / 100))
|
|
|
|
|
|
class PositionTiltCover(PositionCover, TiltCover):
|
|
"""Representation of a cover entity with position & tilt capabilities."""
|
|
|
|
_attr_supported_features = (
|
|
CoverEntityFeature.OPEN
|
|
| CoverEntityFeature.CLOSE
|
|
| CoverEntityFeature.STOP
|
|
| CoverEntityFeature.SET_POSITION
|
|
| CoverEntityFeature.OPEN_TILT
|
|
| CoverEntityFeature.CLOSE_TILT
|
|
| CoverEntityFeature.STOP_TILT
|
|
| CoverEntityFeature.SET_TILT_POSITION
|
|
)
|
|
|
|
|
|
BLIND_TYPE_TO_CLASS: dict[str, type[MotionblindsBLECoverEntity]] = {
|
|
MotionBlindType.ROLLER.name: PositionCover,
|
|
MotionBlindType.HONEYCOMB.name: PositionCover,
|
|
MotionBlindType.ROMAN.name: PositionCover,
|
|
MotionBlindType.VENETIAN.name: PositionTiltCover,
|
|
MotionBlindType.VENETIAN_TILT_ONLY.name: TiltCover,
|
|
MotionBlindType.DOUBLE_ROLLER.name: PositionTiltCover,
|
|
MotionBlindType.CURTAIN.name: PositionCover,
|
|
MotionBlindType.VERTICAL.name: PositionTiltCover,
|
|
}
|