1
0
mirror of https://github.com/home-assistant/core.git synced 2025-04-29 19:57:52 +00:00

289 lines
10 KiB
Python

"""Support for binary sensors through the SmartThings cloud API."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from pysmartthings import Attribute, Capability, Category, SmartThings, Status
from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR_DOMAIN,
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import FullDevice, SmartThingsConfigEntry
from .const import INVALID_SWITCH_CATEGORIES, MAIN
from .entity import SmartThingsEntity
from .util import deprecate_entity
@dataclass(frozen=True, kw_only=True)
class SmartThingsBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describe a SmartThings binary sensor entity."""
is_on_key: str
category_device_class: dict[Category | str, BinarySensorDeviceClass] | None = None
category: set[Category] | None = None
exists_fn: Callable[[str], bool] | None = None
component_translation_key: dict[str, str] | None = None
deprecated_fn: Callable[
[dict[str, dict[Capability | str, dict[Attribute | str, Status]]]], str | None
] = lambda _: None
CAPABILITY_TO_SENSORS: dict[
Capability, dict[Attribute, SmartThingsBinarySensorEntityDescription]
] = {
Capability.ACCELERATION_SENSOR: {
Attribute.ACCELERATION: SmartThingsBinarySensorEntityDescription(
key=Attribute.ACCELERATION,
translation_key="acceleration",
device_class=BinarySensorDeviceClass.MOVING,
is_on_key="active",
)
},
Capability.CONTACT_SENSOR: {
Attribute.CONTACT: SmartThingsBinarySensorEntityDescription(
key=Attribute.CONTACT,
device_class=BinarySensorDeviceClass.DOOR,
is_on_key="open",
category_device_class={
Category.GARAGE_DOOR: BinarySensorDeviceClass.GARAGE_DOOR,
Category.DOOR: BinarySensorDeviceClass.DOOR,
Category.WINDOW: BinarySensorDeviceClass.WINDOW,
},
exists_fn=lambda key: key in {"freezer", "cooler", "cvroom"},
component_translation_key={
"freezer": "freezer_door",
"cooler": "cooler_door",
"cvroom": "cool_select_plus_door",
},
deprecated_fn=(
lambda status: "fridge_door"
if "freezer" in status and "cooler" in status
else None
),
)
},
Capability.CUSTOM_DRYER_WRINKLE_PREVENT: {
Attribute.OPERATING_STATE: SmartThingsBinarySensorEntityDescription(
key=Attribute.OPERATING_STATE,
translation_key="dryer_wrinkle_prevent_active",
is_on_key="running",
entity_category=EntityCategory.DIAGNOSTIC,
)
},
Capability.FILTER_STATUS: {
Attribute.FILTER_STATUS: SmartThingsBinarySensorEntityDescription(
key=Attribute.FILTER_STATUS,
translation_key="filter_status",
device_class=BinarySensorDeviceClass.PROBLEM,
is_on_key="replace",
)
},
Capability.SAMSUNG_CE_KIDS_LOCK: {
Attribute.LOCK_STATE: SmartThingsBinarySensorEntityDescription(
key=Attribute.LOCK_STATE,
translation_key="child_lock",
is_on_key="locked",
)
},
Capability.MOTION_SENSOR: {
Attribute.MOTION: SmartThingsBinarySensorEntityDescription(
key=Attribute.MOTION,
device_class=BinarySensorDeviceClass.MOTION,
is_on_key="active",
)
},
Capability.PRESENCE_SENSOR: {
Attribute.PRESENCE: SmartThingsBinarySensorEntityDescription(
key=Attribute.PRESENCE,
device_class=BinarySensorDeviceClass.PRESENCE,
is_on_key="present",
)
},
Capability.REMOTE_CONTROL_STATUS: {
Attribute.REMOTE_CONTROL_ENABLED: SmartThingsBinarySensorEntityDescription(
key=Attribute.REMOTE_CONTROL_ENABLED,
translation_key="remote_control",
is_on_key="true",
)
},
Capability.SOUND_SENSOR: {
Attribute.SOUND: SmartThingsBinarySensorEntityDescription(
key=Attribute.SOUND,
device_class=BinarySensorDeviceClass.SOUND,
is_on_key="detected",
)
},
Capability.SWITCH: {
Attribute.SWITCH: SmartThingsBinarySensorEntityDescription(
key=Attribute.SWITCH,
device_class=BinarySensorDeviceClass.POWER,
is_on_key="on",
category=INVALID_SWITCH_CATEGORIES,
)
},
Capability.TAMPER_ALERT: {
Attribute.TAMPER: SmartThingsBinarySensorEntityDescription(
key=Attribute.TAMPER,
device_class=BinarySensorDeviceClass.TAMPER,
is_on_key="detected",
entity_category=EntityCategory.DIAGNOSTIC,
)
},
Capability.VALVE: {
Attribute.VALVE: SmartThingsBinarySensorEntityDescription(
key=Attribute.VALVE,
translation_key="valve",
device_class=BinarySensorDeviceClass.OPENING,
is_on_key="open",
deprecated_fn=lambda _: "valve",
)
},
Capability.WATER_SENSOR: {
Attribute.WATER: SmartThingsBinarySensorEntityDescription(
key=Attribute.WATER,
device_class=BinarySensorDeviceClass.MOISTURE,
is_on_key="wet",
)
},
Capability.SAMSUNG_CE_DOOR_STATE: {
Attribute.DOOR_STATE: SmartThingsBinarySensorEntityDescription(
key=Attribute.DOOR_STATE,
translation_key="door",
device_class=BinarySensorDeviceClass.OPENING,
is_on_key="open",
)
},
}
def get_main_component_category(
device: FullDevice,
) -> Category | str:
"""Get the main component of a device."""
main = device.device.components[MAIN]
return main.user_category or main.manufacturer_category
async def async_setup_entry(
hass: HomeAssistant,
entry: SmartThingsConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add binary sensors for a config entry."""
entry_data = entry.runtime_data
entities = []
entity_registry = er.async_get(hass)
for device in entry_data.devices.values(): # pylint: disable=too-many-nested-blocks
for capability, attribute_map in CAPABILITY_TO_SENSORS.items():
for attribute, description in attribute_map.items():
for component in device.status:
if (
capability in device.status[component]
and (
component == MAIN
or (
description.exists_fn is not None
and description.exists_fn(component)
)
)
and (
not description.category
or get_main_component_category(device)
in description.category
)
):
if (
component == MAIN
and (issue := description.deprecated_fn(device.status))
is not None
):
if deprecate_entity(
hass,
entity_registry,
BINARY_SENSOR_DOMAIN,
f"{device.device.device_id}_{component}_{capability}_{attribute}_{attribute}",
f"deprecated_binary_{issue}",
):
entities.append(
SmartThingsBinarySensor(
entry_data.client,
device,
description,
capability,
attribute,
component,
)
)
continue
entities.append(
SmartThingsBinarySensor(
entry_data.client,
device,
description,
capability,
attribute,
component,
)
)
async_add_entities(entities)
class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorEntity):
"""Define a SmartThings Binary Sensor."""
entity_description: SmartThingsBinarySensorEntityDescription
def __init__(
self,
client: SmartThings,
device: FullDevice,
entity_description: SmartThingsBinarySensorEntityDescription,
capability: Capability,
attribute: Attribute,
component: str,
) -> None:
"""Init the class."""
super().__init__(client, device, {capability}, component=component)
self._attribute = attribute
self.capability = capability
self.entity_description = entity_description
self._attr_unique_id = f"{device.device.device_id}_{component}_{capability}_{attribute}_{attribute}"
if (
entity_description.category_device_class
and (category := get_main_component_category(device))
in entity_description.category_device_class
):
self._attr_device_class = entity_description.category_device_class[category]
self._attr_name = None
if (
entity_description.component_translation_key is not None
and (
translation_key := entity_description.component_translation_key.get(
component
)
)
is not None
):
self._attr_translation_key = translation_key
@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
return (
self.get_attribute_value(self.capability, self._attribute)
== self.entity_description.is_on_key
)