core/homeassistant/components/screenlogic/entity.py

176 lines
6.1 KiB
Python

"""Base ScreenLogicEntity definitions."""
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
import logging
from typing import Any
from screenlogicpy import ScreenLogicGateway
from screenlogicpy.const.common import (
ON_OFF,
ScreenLogicCommunicationError,
ScreenLogicError,
)
from screenlogicpy.const.data import ATTR
from screenlogicpy.const.msg import CODE
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ScreenLogicDataPath
from .coordinator import ScreenlogicDataUpdateCoordinator
from .util import generate_unique_id
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True, kw_only=True)
class ScreenLogicEntityDescription(EntityDescription):
"""Base class for a ScreenLogic entity description."""
data_root: ScreenLogicDataPath
enabled_lambda: Callable[..., bool] | None = None
class ScreenLogicEntity(CoordinatorEntity[ScreenlogicDataUpdateCoordinator]):
"""Base class for all ScreenLogic entities."""
entity_description: ScreenLogicEntityDescription
_attr_has_entity_name = True
def __init__(
self,
coordinator: ScreenlogicDataUpdateCoordinator,
entity_description: ScreenLogicEntityDescription,
) -> None:
"""Initialize of the entity."""
super().__init__(coordinator)
self.entity_description = entity_description
self._data_key = self.entity_description.key
self._data_path = (*self.entity_description.data_root, self._data_key)
mac = self.mac
self._attr_unique_id = f"{mac}_{generate_unique_id(*self._data_path)}"
self._attr_name = self.entity_data[ATTR.NAME]
assert mac is not None
self._attr_device_info = DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, mac)},
manufacturer="Pentair",
model=self.gateway.controller_model,
name=self.gateway.name,
sw_version=self.gateway.version,
)
@property
def mac(self) -> str | None:
"""Mac address."""
assert self.coordinator.config_entry is not None
return self.coordinator.config_entry.unique_id
@property
def gateway(self) -> ScreenLogicGateway:
"""Return the gateway."""
return self.coordinator.gateway
async def _async_refresh(self) -> None:
"""Refresh the data from the gateway."""
await self.coordinator.async_refresh()
# Second debounced refresh to catch any secondary
# changes in the device
await self.coordinator.async_request_refresh()
async def _async_refresh_timed(self, now: datetime) -> None:
"""Refresh from a timed called."""
await self.coordinator.async_request_refresh()
@property
def entity_data(self) -> dict:
"""Shortcut to the data for this entity."""
try:
return self.gateway.get_data(*self._data_path, strict=True)
except KeyError as ke:
raise HomeAssistantError(f"Data not found: {self._data_path}") from ke
@dataclass(frozen=True, kw_only=True)
class ScreenLogicPushEntityDescription(ScreenLogicEntityDescription):
"""Base class for a ScreenLogic push entity description."""
subscription_code: CODE
class ScreenLogicPushEntity(ScreenLogicEntity):
"""Base class for all ScreenLogic push entities."""
entity_description: ScreenLogicPushEntityDescription
def __init__(
self,
coordinator: ScreenlogicDataUpdateCoordinator,
entity_description: ScreenLogicPushEntityDescription,
) -> None:
"""Initialize of the entity."""
super().__init__(coordinator, entity_description)
self._subscription_code = entity_description.subscription_code
self._last_update_success = True
@callback
def _async_data_updated(self) -> None:
"""Handle data updates."""
self._last_update_success = self.coordinator.last_update_success
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
await self.gateway.async_subscribe_client(
self._async_data_updated,
self._subscription_code,
)
)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
# For push entities, only take updates from the coordinator if availability changes.
if self.coordinator.last_update_success != self._last_update_success:
self._async_data_updated()
class ScreenLogicSwitchingEntity(ScreenLogicEntity):
"""Base class for all switchable entities."""
@property
def is_on(self) -> bool:
"""Get whether the switch is in on state."""
return self.entity_data[ATTR.VALUE] == ON_OFF.ON
async def async_turn_on(self, **kwargs: Any) -> None:
"""Send the ON command."""
await self._async_set_state(ON_OFF.ON)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Send the OFF command."""
await self._async_set_state(ON_OFF.OFF)
async def _async_set_state(self, state: ON_OFF) -> None:
raise NotImplementedError
class ScreenLogicCircuitEntity(ScreenLogicSwitchingEntity, ScreenLogicPushEntity):
"""Base class for all ScreenLogic circuit switch and light entities."""
async def _async_set_state(self, state: ON_OFF) -> None:
try:
await self.gateway.async_set_circuit(self._data_key, state.value)
except (ScreenLogicCommunicationError, ScreenLogicError) as sle:
raise HomeAssistantError(
f"Failed to set_circuit {self._data_key} {state.value}: {sle.msg}"
) from sle
_LOGGER.debug("Set circuit %s %s", self._data_key, state.value)