From b2cef78d90ea06f3327181ec27cf178b794f7641 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 13 Oct 2021 22:12:07 +0200 Subject: [PATCH] Add binary sensor platform to Tuya (#57623) --- .coveragerc | 1 + .../components/tuya/binary_sensor.py | 98 +++++++++++++++++++ homeassistant/components/tuya/const.py | 5 +- 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/tuya/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index 5db28077014..0f453518adc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1113,6 +1113,7 @@ omit = homeassistant/components/travisci/sensor.py homeassistant/components/tuya/__init__.py homeassistant/components/tuya/base.py + homeassistant/components/tuya/binary_sensor.py homeassistant/components/tuya/climate.py homeassistant/components/tuya/const.py homeassistant/components/tuya/fan.py diff --git a/homeassistant/components/tuya/binary_sensor.py b/homeassistant/components/tuya/binary_sensor.py new file mode 100644 index 00000000000..90b3d36c564 --- /dev/null +++ b/homeassistant/components/tuya/binary_sensor.py @@ -0,0 +1,98 @@ +"""Support for Tuya binary sensors.""" +from __future__ import annotations + +from tuya_iot import TuyaDevice, TuyaDeviceManager + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_DOOR, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import HomeAssistantTuyaData +from .base import TuyaHaEntity +from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode + +# All descriptions can be found here. Mostly the Boolean data types in the +# default status set of each category (that don't have a set instruction) +# end up being a binary sensor. +# https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq +BINARY_SENSORS: dict[str, tuple[BinarySensorEntityDescription, ...]] = { + # Door Window Sensor + # https://developer.tuya.com/en/docs/iot/s?id=K9gf48hm02l8m + "mcs": ( + BinarySensorEntityDescription( + key=DPCode.DOORCONTACT_STATE, + device_class=DEVICE_CLASS_DOOR, + ), + BinarySensorEntityDescription( + key=DPCode.TEMPER_ALARM, + name="Tamper", + entity_registry_enabled_default=False, + ), + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Tuya binary sensor dynamically through Tuya discovery.""" + hass_data: HomeAssistantTuyaData = hass.data[DOMAIN][entry.entry_id] + + @callback + def async_discover_device(device_ids: list[str]) -> None: + """Discover and add a discovered Tuya binary sensor.""" + entities: list[TuyaBinarySensorEntity] = [] + for device_id in device_ids: + device = hass_data.device_manager.device_map[device_id] + if descriptions := BINARY_SENSORS.get(device.category): + for description in descriptions: + if ( + description.key in device.function + or description.key in device.status + ): + entities.append( + TuyaBinarySensorEntity( + device, hass_data.device_manager, description + ) + ) + + async_add_entities(entities) + + async_discover_device([*hass_data.device_manager.device_map]) + + entry.async_on_unload( + async_dispatcher_connect(hass, TUYA_DISCOVERY_NEW, async_discover_device) + ) + + +class TuyaBinarySensorEntity(TuyaHaEntity, BinarySensorEntity): + """Tuya Binary Sensor Entity.""" + + def __init__( + self, + device: TuyaDevice, + device_manager: TuyaDeviceManager, + description: BinarySensorEntityDescription, + ) -> None: + """Init Tuya binary sensor.""" + super().__init__(device, device_manager) + self.entity_description = description + self._attr_unique_id = f"{super().unique_id}{description.key}" + + @property + def name(self) -> str | None: + """Return Tuya device name.""" + if self.entity_description.name is not None: + return f"{self.tuya_device.name} {self.entity_description.name}" + return self.tuya_device.name + + @property + def is_on(self) -> bool: + """Return true if sensor is on.""" + return self.tuya_device.status.get(self.entity_description.key, False) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 73c7498cc40..0efdb10b6a2 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -41,6 +41,7 @@ TUYA_SUPPORTED_PRODUCT_CATEGORIES = ( "kj", # Air Purifier "kj", # Air Purifier "kt", # Air conditioner + "mcs", # Door Window Sensor "pc", # Power Strip "qn", # Heater "wk", # Thermostat @@ -52,7 +53,7 @@ TUYA_SUPPORTED_PRODUCT_CATEGORIES = ( TUYA_SMART_APP = "tuyaSmart" SMARTLIFE_APP = "smartlife" -PLATFORMS = ["climate", "fan", "light", "scene", "switch"] +PLATFORMS = ["binary_sensor", "climate", "fan", "light", "scene", "switch"] class DPCode(str, Enum): @@ -67,6 +68,7 @@ class DPCode(str, Enum): CHILD_LOCK = "child_lock" # Child lock COLOUR_DATA = "colour_data" # Colored light mode COLOUR_DATA_V2 = "colour_data_v2" # Colored light mode + DOORCONTACT_STATE = "doorcontact_state" # Status of door window sensor FAN_DIRECTION = "fan_direction" # Fan direction FAN_SPEED_ENUM = "fan_speed_enum" # Speed mode FAN_SPEED_PERCENT = "fan_speed_percent" # Stepless speed @@ -106,6 +108,7 @@ class DPCode(str, Enum): TEMP_SET_F = "temp_set_f" # Set the temperature in °F TEMP_UNIT_CONVERT = "temp_unit_convert" # Temperature unit switching TEMP_VALUE = "temp_value" # Color temperature + TEMPER_ALARM = "temper_alarm" # Tamper alarm UV = "uv" # UV sterilization WARM = "warm" # Heat preservation WATER_RESET = "water_reset" # Resetting of water usage days