mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
314 lines
11 KiB
Python
314 lines
11 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.automation import automations_with_entity
|
|
from homeassistant.components.binary_sensor import (
|
|
BinarySensorDeviceClass,
|
|
BinarySensorEntity,
|
|
BinarySensorEntityDescription,
|
|
)
|
|
from homeassistant.components.script import scripts_with_entity
|
|
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 homeassistant.helpers.issue_registry import (
|
|
IssueSeverity,
|
|
async_create_issue,
|
|
async_delete_issue,
|
|
)
|
|
|
|
from . import FullDevice, SmartThingsConfigEntry
|
|
from .const import DOMAIN, MAIN
|
|
from .entity import SmartThingsEntity
|
|
|
|
|
|
@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"},
|
|
component_translation_key={
|
|
"freezer": "freezer_door",
|
|
"cooler": "cooler_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={
|
|
Category.CLOTHING_CARE_MACHINE,
|
|
Category.COOKTOP,
|
|
Category.DISHWASHER,
|
|
Category.DRYER,
|
|
Category.MICROWAVE,
|
|
Category.WASHER,
|
|
},
|
|
)
|
|
},
|
|
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
|
|
async_add_entities(
|
|
SmartThingsBinarySensor(
|
|
entry_data.client, device, description, capability, attribute, component
|
|
)
|
|
for device in entry_data.devices.values()
|
|
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
|
|
)
|
|
)
|
|
|
|
|
|
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
|
|
)
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""Call when entity is added to hass."""
|
|
await super().async_added_to_hass()
|
|
if (issue := self.entity_description.deprecated_fn(self.device.status)) is None:
|
|
return
|
|
automations = automations_with_entity(self.hass, self.entity_id)
|
|
scripts = scripts_with_entity(self.hass, self.entity_id)
|
|
items = automations + scripts
|
|
if not items:
|
|
return
|
|
|
|
entity_reg: er.EntityRegistry = er.async_get(self.hass)
|
|
entity_automations = [
|
|
automation_entity
|
|
for automation_id in automations
|
|
if (automation_entity := entity_reg.async_get(automation_id))
|
|
]
|
|
entity_scripts = [
|
|
script_entity
|
|
for script_id in scripts
|
|
if (script_entity := entity_reg.async_get(script_id))
|
|
]
|
|
|
|
items_list = [
|
|
f"- [{item.original_name}](/config/automation/edit/{item.unique_id})"
|
|
for item in entity_automations
|
|
] + [
|
|
f"- [{item.original_name}](/config/script/edit/{item.unique_id})"
|
|
for item in entity_scripts
|
|
]
|
|
|
|
async_create_issue(
|
|
self.hass,
|
|
DOMAIN,
|
|
f"deprecated_binary_{issue}_{self.entity_id}",
|
|
breaks_in_ha_version="2025.10.0",
|
|
is_fixable=False,
|
|
severity=IssueSeverity.WARNING,
|
|
translation_key=f"deprecated_binary_{issue}",
|
|
translation_placeholders={
|
|
"entity": self.entity_id,
|
|
"items": "\n".join(items_list),
|
|
},
|
|
)
|
|
|
|
async def async_will_remove_from_hass(self) -> None:
|
|
"""Call when entity will be removed from hass."""
|
|
await super().async_will_remove_from_hass()
|
|
if (issue := self.entity_description.deprecated_fn(self.device.status)) is None:
|
|
return
|
|
async_delete_issue(
|
|
self.hass, DOMAIN, f"deprecated_binary_{issue}_{self.entity_id}"
|
|
)
|