From 8b216db88526cae66776e9787f44281859497af8 Mon Sep 17 00:00:00 2001 From: Tuen Lee Date: Fri, 7 Jan 2022 14:20:30 +0100 Subject: [PATCH] Tuya Alarm Initial commit (#60080) --- .coveragerc | 1 + .../components/tuya/alarm_control_panel.py | 140 ++++++++++++++++++ homeassistant/components/tuya/const.py | 2 + 3 files changed, 143 insertions(+) create mode 100644 homeassistant/components/tuya/alarm_control_panel.py diff --git a/.coveragerc b/.coveragerc index d987c63b516..a979845326b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1174,6 +1174,7 @@ omit = homeassistant/components/transmission/errors.py homeassistant/components/travisci/sensor.py homeassistant/components/tuya/__init__.py + homeassistant/components/tuya/alarm_control_panel.py homeassistant/components/tuya/base.py homeassistant/components/tuya/binary_sensor.py homeassistant/components/tuya/button.py diff --git a/homeassistant/components/tuya/alarm_control_panel.py b/homeassistant/components/tuya/alarm_control_panel.py new file mode 100644 index 00000000000..f073184882a --- /dev/null +++ b/homeassistant/components/tuya/alarm_control_panel.py @@ -0,0 +1,140 @@ +"""Support for Tuya Alarm.""" +from __future__ import annotations + +from tuya_iot import TuyaDevice, TuyaDeviceManager + +from homeassistant.backports.enum import StrEnum +from homeassistant.components.alarm_control_panel import ( + SUPPORT_ALARM_ARM_AWAY, + SUPPORT_ALARM_ARM_HOME, + SUPPORT_ALARM_TRIGGER, + AlarmControlPanelEntity, + AlarmControlPanelEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, +) +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 EnumTypeData, TuyaEntity +from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode + + +class Mode(StrEnum): + """Alarm modes.""" + + ARM = "arm" + DISARMED = "disarmed" + HOME = "home" + SOS = "sos" + + +STATE_MAPPING: dict[str, str] = { + Mode.DISARMED: STATE_ALARM_DISARMED, + Mode.ARM: STATE_ALARM_ARMED_AWAY, + Mode.HOME: STATE_ALARM_ARMED_HOME, + Mode.SOS: STATE_ALARM_TRIGGERED, +} + + +# All descriptions can be found here: +# https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq +ALARM: dict[str, tuple[AlarmControlPanelEntityDescription, ...]] = { + # Alarm Host + # https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf + "mal": ( + AlarmControlPanelEntityDescription( + key=DPCode.MASTER_MODE, + name="Alarm", + ), + ) +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Tuya alarm 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 siren.""" + entities: list[TuyaAlarmEntity] = [] + for device_id in device_ids: + device = hass_data.device_manager.device_map[device_id] + if descriptions := ALARM.get(device.category): + for description in descriptions: + if description.key in device.status: + entities.append( + TuyaAlarmEntity( + 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 TuyaAlarmEntity(TuyaEntity, AlarmControlPanelEntity): + """Tuya Alarm Entity.""" + + _attr_icon = "mdi:security" + + def __init__( + self, + device: TuyaDevice, + device_manager: TuyaDeviceManager, + description: AlarmControlPanelEntityDescription, + ) -> None: + """Init Tuya Alarm.""" + self._attr_supported_features = 0 + super().__init__(device, device_manager) + self.entity_description = description + self._attr_unique_id = f"{super().unique_id}{description.key}" + + # Determine supported modes + supported_mode = EnumTypeData.from_json( + device.function[DPCode.MASTER_MODE].values + ).range + + if Mode.HOME in supported_mode: + self._attr_supported_features |= SUPPORT_ALARM_ARM_HOME + + if Mode.ARM in supported_mode: + self._attr_supported_features |= SUPPORT_ALARM_ARM_AWAY + + if Mode.SOS in supported_mode: + self._attr_supported_features |= SUPPORT_ALARM_TRIGGER + + @property + def state(self): + """Return the state of the device.""" + return STATE_MAPPING.get(self.device.status.get(DPCode.MASTER_MODE)) + + def alarm_disarm(self, code: str | None = None) -> None: + """Send Disarm command.""" + self._send_command([{"code": DPCode.MASTER_MODE, "value": Mode.DISARMED}]) + + def alarm_arm_home(self, code: str | None = None) -> None: + """Send Home command.""" + self._send_command([{"code": DPCode.MASTER_MODE, "value": Mode.HOME}]) + + def alarm_arm_away(self, code: str | None = None) -> None: + """Send Arm command.""" + self._send_command([{"code": DPCode.MASTER_MODE, "value": Mode.ARM}]) + + def alarm_trigger(self, code: str | None = None) -> None: + """Send SOS command.""" + self._send_command([{"code": DPCode.MASTER_MODE, "value": Mode.SOS}]) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index c66cf5686ea..bc881636eff 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -80,6 +80,7 @@ TUYA_SMART_APP = "tuyaSmart" SMARTLIFE_APP = "smartlife" PLATFORMS = [ + Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CAMERA, @@ -210,6 +211,7 @@ class DPCode(StrEnum): LIGHT = "light" # Light LIGHT_MODE = "light_mode" LOCK = "lock" # Lock / Child lock + MASTER_MODE = "master_mode" # alarm mode MACH_OPERATE = "mach_operate" MANUAL_FEED = "manual_feed" MATERIAL = "material" # Material