"""Implement the Yandex Smart Home event properties.""" from __future__ import annotations from abc import ABC import logging from typing import Any from homeassistant.components import binary_sensor, sensor from homeassistant.const import ATTR_DEVICE_CLASS, STATE_ON, STATE_OPEN, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State from . import const from .const import ERR_NOT_SUPPORTED_IN_CURRENT_MODE, STATE_EMPTY, STATE_NONE, STATE_NONE_UI from .error import SmartHomeError from .helpers import Config from .prop import PREFIX_PROPERTIES, AbstractProperty, register_property _LOGGER = logging.getLogger(__name__) PROPERTY_EVENT = PREFIX_PROPERTIES + 'event' PROPERTY_EVENT_VALUES = { const.EVENT_INSTANCE_VIBRATION: [ const.EVENT_VIBRATION_VIBRATION, const.EVENT_VIBRATION_TILT, const.EVENT_VIBRATION_FALL, ], const.EVENT_INSTANCE_OPEN: [ const.EVENT_OPEN_OPENED, const.EVENT_OPEN_CLOSED, ], const.EVENT_INSTANCE_BUTTON: [ const.EVENT_BUTTON_CLICK, const.EVENT_BUTTON_DOUBLE_CLICK, const.EVENT_BUTTON_LONG_PRESS, ], const.EVENT_INSTANCE_MOTION: [ const.EVENT_MOTION_DETECTED, const.EVENT_MOTION_NOT_DETECTED, ], const.EVENT_INSTANCE_SMOKE: [ const.EVENT_SMOKE_DETECTED, const.EVENT_SMOKE_NOT_DETECTED, const.EVENT_SMOKE_HIGH, ], const.EVENT_INSTANCE_GAS: [ const.EVENT_GAS_DETECTED, const.EVENT_GAS_NOT_DETECTED, const.EVENT_GAS_HIGH, ], const.EVENT_INSTANCE_BATTERY_LEVEL: [ const.EVENT_BATTERY_LEVEL_LOW, const.EVENT_BATTERY_LEVEL_NORMAL, ], const.EVENT_INSTANCE_WATER_LEVEL: [ const.EVENT_WATER_LEVEL_LOW, const.EVENT_WATER_LEVEL_NORMAL, ], const.EVENT_INSTANCE_WATER_LEAK: [ const.EVENT_WATER_LEAK_LEAK, const.EVENT_WATER_LEAK_DRY, ], } class EventProperty(AbstractProperty, ABC): type = PROPERTY_EVENT report_immediately = True def __init__(self, hass: HomeAssistant, config: Config, state: State): super().__init__(hass, config, state) self.values = PROPERTY_EVENT_VALUES.get(self.instance) def parameters(self) -> dict[str, Any]: return { 'instance': self.instance, 'events': [ {'value': v} for v in self.values ] } if self.values else {} @staticmethod def bool_value(value) -> bool: """Return the bool value according to any type of value.""" return value in [1, STATE_ON, STATE_OPEN, 'high', True] def event_value(self, value) -> str | None: if str(value).lower() in (STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_NONE, STATE_NONE_UI, STATE_EMPTY): return None if self.instance == const.EVENT_INSTANCE_OPEN: return const.EVENT_OPEN_OPENED if self.bool_value(value) else const.EVENT_OPEN_CLOSED elif self.instance == const.EVENT_INSTANCE_MOTION: return const.EVENT_MOTION_DETECTED if self.bool_value(value) else const.EVENT_MOTION_NOT_DETECTED elif self.instance == const.EVENT_INSTANCE_SMOKE: if value == 'high': return const.EVENT_SMOKE_HIGH return const.EVENT_SMOKE_DETECTED if self.bool_value(value) else const.EVENT_SMOKE_NOT_DETECTED elif self.instance == const.EVENT_INSTANCE_GAS: if value == 'high': return const.EVENT_GAS_HIGH return const.EVENT_GAS_DETECTED if self.bool_value(value) else const.EVENT_GAS_NOT_DETECTED elif self.instance in const.EVENT_INSTANCE_BATTERY_LEVEL: return const.EVENT_BATTERY_LEVEL_LOW if self.bool_value(value) else const.EVENT_BATTERY_LEVEL_NORMAL elif self.instance in const.EVENT_INSTANCE_WATER_LEVEL: return const.EVENT_WATER_LEVEL_LOW if self.bool_value(value) else const.EVENT_WATER_LEVEL_NORMAL elif self.instance == const.EVENT_INSTANCE_WATER_LEAK: return const.EVENT_WATER_LEAK_LEAK if self.bool_value(value) else const.EVENT_WATER_LEAK_DRY elif self.instance in const.EVENT_INSTANCE_BUTTON: if value in ['single', 'click']: return const.EVENT_BUTTON_CLICK elif value in ['double', 'double_click']: return const.EVENT_BUTTON_DOUBLE_CLICK elif value in ['long', 'long_click', 'long_click_press', 'hold']: return const.EVENT_BUTTON_LONG_PRESS elif self.instance in const.EVENT_INSTANCE_VIBRATION: if value in ['vibrate', 'vibration', 'actively', 'move', 'tap_twice', 'shake_air', 'swing']: return const.EVENT_VIBRATION_VIBRATION elif self.bool_value(value): return const.EVENT_VIBRATION_VIBRATION elif value in ['tilt', 'flip90', 'flip180', 'rotate']: return const.EVENT_VIBRATION_TILT elif value in ['free_fall', 'drop']: return const.EVENT_VIBRATION_FALL def get_value(self) -> str | None: if self.state.domain == binary_sensor.DOMAIN: return self.event_value(self.state.state) raise SmartHomeError( ERR_NOT_SUPPORTED_IN_CURRENT_MODE, f'Failed to get value for instance {self.instance} of {self.state.entity_id}' ) @register_property class ContactProperty(EventProperty): instance = const.EVENT_INSTANCE_OPEN def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get(ATTR_DEVICE_CLASS) in ( binary_sensor.DEVICE_CLASS_DOOR, binary_sensor.DEVICE_CLASS_GARAGE_DOOR, binary_sensor.DEVICE_CLASS_WINDOW, binary_sensor.DEVICE_CLASS_OPENING ) return False @register_property class MotionProperty(EventProperty): instance = const.EVENT_INSTANCE_MOTION def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get(ATTR_DEVICE_CLASS) in ( binary_sensor.DEVICE_CLASS_MOTION, binary_sensor.DEVICE_CLASS_OCCUPANCY, binary_sensor.DEVICE_CLASS_PRESENCE ) return False @register_property class GasProperty(EventProperty): instance = const.EVENT_INSTANCE_GAS def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_GAS return False @register_property class SmokeProperty(EventProperty): instance = const.EVENT_INSTANCE_SMOKE def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_SMOKE return False @register_property class BatteryLevelLowProperty(EventProperty): instance = const.EVENT_INSTANCE_BATTERY_LEVEL def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_BATTERY return False @register_property class WaterLevelLowProperty(EventProperty): instance = const.EVENT_INSTANCE_WATER_LEVEL def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get(ATTR_DEVICE_CLASS) == 'water_level' return False @register_property class WaterLeakProperty(EventProperty): instance = const.EVENT_INSTANCE_WATER_LEAK def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_MOISTURE return False @register_property class ButtonProperty(EventProperty): instance = const.EVENT_INSTANCE_BUTTON retrievable = False def supported(self) -> bool: if self.state.attributes.get(ATTR_DEVICE_CLASS) == const.DEVICE_CLASS_BUTTON: return True if self.entity_config.get(const.CONF_DEVICE_CLASS) == const.DEVICE_CLASS_BUTTON: return True if self.state.domain == binary_sensor.DOMAIN: return self.state.attributes.get('last_action') in [ 'single', 'click', 'double', 'double_click', 'long', 'long_click', 'long_click_press', 'long_click_release', 'hold', 'release', 'triple', 'quadruple', 'many' ] if self.state.domain == sensor.DOMAIN: return self.state.attributes.get('action') in [ 'single', 'click', 'double', 'double_click', 'long', 'long_click', 'long_click_press', 'long_click_release', 'hold', 'release', 'triple', 'quadruple', 'many' ] return False def get_value(self) -> str | None: for value in [self.state.attributes.get('last_action'), self.state.attributes.get('action'), self.state.state]: event_value = self.event_value(value) if event_value: return event_value @register_property class VibrationBinarySensorProperty(EventProperty): instance = const.EVENT_INSTANCE_VIBRATION retrievable = False def supported(self) -> bool: if self.state.domain == binary_sensor.DOMAIN: if self.state.attributes.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_VIBRATION: return True return self.state.attributes.get('last_action') in [ 'vibrate', 'tilt', 'free_fall', 'actively', 'move', 'tap_twice', 'shake_air', 'swing', 'flip90', 'flip180', 'rotate', 'drop' ] return False def get_value(self) -> str | None: if self.state.attributes.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_VIBRATION: return self.event_value(self.state.state) return self.event_value(self.state.attributes.get('last_action')) @register_property class VibrationSensorProperty(EventProperty): instance = const.EVENT_INSTANCE_VIBRATION retrievable = False def supported(self) -> bool: if self.state.domain == sensor.DOMAIN: return self.state.attributes.get('action') in [ 'vibrate', 'tilt', 'free_fall', 'actively', 'move', 'tap_twice', 'shake_air', 'swing', 'flip90', 'flip180', 'rotate', 'drop' ] return False def get_value(self) -> str | None: return self.event_value(self.state.attributes.get('action'))