diff --git a/.coveragerc b/.coveragerc index 0b73599dffa7e11c8223e35d2f57baa7721eb271..f4794b593816f112c19e4715cb311dd49d1472ee 100644 --- a/.coveragerc +++ b/.coveragerc @@ -676,6 +676,7 @@ omit = homeassistant/components/systemmonitor/sensor.py homeassistant/components/tado/* homeassistant/components/tado/device_tracker.py + homeassistant/components/tahoma/* homeassistant/components/tank_utility/sensor.py homeassistant/components/tapsaff/binary_sensor.py homeassistant/components/tautulli/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 392c363c648c288ecbd14afd6db09cf4c582d6a6..4fbdca20686615750a112f75471d3da7d6fc651e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -318,6 +318,7 @@ homeassistant/components/syncthru/* @nielstron homeassistant/components/synology_srm/* @aerialls homeassistant/components/syslog/* @fabaff homeassistant/components/tado/* @michaelarnauts +homeassistant/components/tahoma/* @philklei homeassistant/components/tautulli/* @ludeeus homeassistant/components/tellduslive/* @fredrike homeassistant/components/template/* @PhracturedBlue diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..640cc6418d0f01af52aaf0830d794abd3c4320aa --- /dev/null +++ b/homeassistant/components/tahoma/__init__.py @@ -0,0 +1,140 @@ +"""Support for Tahoma devices.""" +from collections import defaultdict +import logging + +from requests.exceptions import RequestException +from tahoma_api import Action, TahomaApi +import voluptuous as vol + +from homeassistant.const import CONF_EXCLUDE, CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers import config_validation as cv, discovery +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "tahoma" + +TAHOMA_ID_FORMAT = "{}_{}" + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_EXCLUDE, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + +TAHOMA_COMPONENTS = ["scene", "sensor", "cover", "switch", "binary_sensor"] + +TAHOMA_TYPES = { + "io:ExteriorVenetianBlindIOComponent": "cover", + "io:HorizontalAwningIOComponent": "cover", + "io:LightIOSystemSensor": "sensor", + "io:OnOffIOComponent": "switch", + "io:OnOffLightIOComponent": "switch", + "io:RollerShutterGenericIOComponent": "cover", + "io:RollerShutterUnoIOComponent": "cover", + "io:RollerShutterVeluxIOComponent": "cover", + "io:RollerShutterWithLowSpeedManagementIOComponent": "cover", + "io:SomfyBasicContactIOSystemSensor": "sensor", + "io:SomfyContactIOSystemSensor": "sensor", + "io:VerticalExteriorAwningIOComponent": "cover", + "io:VerticalInteriorBlindVeluxIOComponent": "cover", + "io:WindowOpenerVeluxIOComponent": "cover", + "io:GarageOpenerIOComponent": "cover", + "io:DiscreteGarageOpenerIOComponent": "cover", + "rtds:RTDSContactSensor": "sensor", + "rtds:RTDSMotionSensor": "sensor", + "rtds:RTDSSmokeSensor": "smoke", + "rts:BlindRTSComponent": "cover", + "rts:CurtainRTSComponent": "cover", + "rts:DualCurtainRTSComponent": "cover", + "rts:ExteriorVenetianBlindRTSComponent": "cover", + "rts:GarageDoor4TRTSComponent": "switch", + "rts:RollerShutterRTSComponent": "cover", + "rts:VenetianBlindRTSComponent": "cover", +} + + +def setup(hass, config): + """Activate Tahoma component.""" + + conf = config[DOMAIN] + username = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASSWORD) + exclude = conf.get(CONF_EXCLUDE) + try: + api = TahomaApi(username, password) + except RequestException: + _LOGGER.exception("Error when trying to log in to the Tahoma API") + return False + + try: + api.get_setup() + devices = api.get_devices() + scenes = api.get_action_groups() + except RequestException: + _LOGGER.exception("Error when getting devices from the Tahoma API") + return False + + hass.data[DOMAIN] = {"controller": api, "devices": defaultdict(list), "scenes": []} + + for device in devices: + _device = api.get_device(device) + if all(ext not in _device.type for ext in exclude): + device_type = map_tahoma_device(_device) + if device_type is None: + _LOGGER.warning( + "Unsupported type %s for Tahoma device %s", + _device.type, + _device.label, + ) + continue + hass.data[DOMAIN]["devices"][device_type].append(_device) + + for scene in scenes: + hass.data[DOMAIN]["scenes"].append(scene) + + for component in TAHOMA_COMPONENTS: + discovery.load_platform(hass, component, DOMAIN, {}, config) + + return True + + +def map_tahoma_device(tahoma_device): + """Map Tahoma device types to Home Assistant components.""" + return TAHOMA_TYPES.get(tahoma_device.type) + + +class TahomaDevice(Entity): + """Representation of a Tahoma device entity.""" + + def __init__(self, tahoma_device, controller): + """Initialize the device.""" + self.tahoma_device = tahoma_device + self.controller = controller + self._name = self.tahoma_device.label + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes of the device.""" + return {"tahoma_device_id": self.tahoma_device.url} + + def apply_action(self, cmd_name, *args): + """Apply Action to Device.""" + + action = Action(self.tahoma_device.url) + action.add_command(cmd_name, *args) + self.controller.apply_actions("HomeAssistant", [action]) diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py new file mode 100644 index 0000000000000000000000000000000000000000..81078ab480babdee8cff9284628c57653a426e86 --- /dev/null +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -0,0 +1,95 @@ +"""Support for Tahoma binary sensors.""" +from datetime import timedelta +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=120) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up Tahoma controller devices.""" + _LOGGER.debug("Setup Tahoma Binary sensor platform") + controller = hass.data[TAHOMA_DOMAIN]["controller"] + devices = [] + for device in hass.data[TAHOMA_DOMAIN]["devices"]["smoke"]: + devices.append(TahomaBinarySensor(device, controller)) + add_entities(devices, True) + + +class TahomaBinarySensor(TahomaDevice, BinarySensorDevice): + """Representation of a Tahoma Binary Sensor.""" + + def __init__(self, tahoma_device, controller): + """Initialize the sensor.""" + super().__init__(tahoma_device, controller) + + self._state = None + self._icon = None + self._battery = None + self._available = False + + @property + def is_on(self): + """Return the state of the sensor.""" + return bool(self._state == STATE_ON) + + @property + def device_class(self): + """Return the class of the device.""" + if self.tahoma_device.type == "rtds:RTDSSmokeSensor": + return "smoke" + return None + + @property + def icon(self): + """Icon for device by its type.""" + return self._icon + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + super_attr = super().device_state_attributes + if super_attr is not None: + attr.update(super_attr) + + if self._battery is not None: + attr[ATTR_BATTERY_LEVEL] = self._battery + return attr + + @property + def available(self): + """Return True if entity is available.""" + return self._available + + def update(self): + """Update the state.""" + self.controller.get_states([self.tahoma_device]) + if self.tahoma_device.type == "rtds:RTDSSmokeSensor": + if self.tahoma_device.active_states["core:SmokeState"] == "notDetected": + self._state = STATE_OFF + else: + self._state = STATE_ON + + if "core:SensorDefectState" in self.tahoma_device.active_states: + # 'lowBattery' for low battery warning. 'dead' for not available. + self._battery = self.tahoma_device.active_states["core:SensorDefectState"] + self._available = bool(self._battery != "dead") + else: + self._battery = None + self._available = True + + if self._state == STATE_ON: + self._icon = "mdi:fire" + elif self._battery == "lowBattery": + self._icon = "mdi:battery-alert" + else: + self._icon = None + + _LOGGER.debug("Update %s, state: %s", self._name, self._state) diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py new file mode 100644 index 0000000000000000000000000000000000000000..e11c2f4cdf59ce4cc3fd4e09ec7db3fb544820dd --- /dev/null +++ b/homeassistant/components/tahoma/cover.py @@ -0,0 +1,249 @@ +"""Support for Tahoma cover - shutters etc.""" +from datetime import timedelta +import logging + +from homeassistant.components.cover import ( + ATTR_POSITION, + DEVICE_CLASS_AWNING, + DEVICE_CLASS_BLIND, + DEVICE_CLASS_CURTAIN, + DEVICE_CLASS_GARAGE, + DEVICE_CLASS_SHUTTER, + DEVICE_CLASS_WINDOW, + CoverDevice, +) +from homeassistant.util.dt import utcnow + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +ATTR_MEM_POS = "memorized_position" +ATTR_RSSI_LEVEL = "rssi_level" +ATTR_LOCK_START_TS = "lock_start_ts" +ATTR_LOCK_END_TS = "lock_end_ts" +ATTR_LOCK_LEVEL = "lock_level" +ATTR_LOCK_ORIG = "lock_originator" + +HORIZONTAL_AWNING = "io:HorizontalAwningIOComponent" + +TAHOMA_DEVICE_CLASSES = { + "io:ExteriorVenetianBlindIOComponent": DEVICE_CLASS_BLIND, + HORIZONTAL_AWNING: DEVICE_CLASS_AWNING, + "io:RollerShutterGenericIOComponent": DEVICE_CLASS_SHUTTER, + "io:RollerShutterUnoIOComponent": DEVICE_CLASS_SHUTTER, + "io:RollerShutterVeluxIOComponent": DEVICE_CLASS_SHUTTER, + "io:RollerShutterWithLowSpeedManagementIOComponent": DEVICE_CLASS_SHUTTER, + "io:VerticalExteriorAwningIOComponent": DEVICE_CLASS_AWNING, + "io:VerticalInteriorBlindVeluxIOComponent": DEVICE_CLASS_BLIND, + "io:WindowOpenerVeluxIOComponent": DEVICE_CLASS_WINDOW, + "io:GarageOpenerIOComponent": DEVICE_CLASS_GARAGE, + "io:DiscreteGarageOpenerIOComponent": DEVICE_CLASS_GARAGE, + "rts:BlindRTSComponent": DEVICE_CLASS_BLIND, + "rts:CurtainRTSComponent": DEVICE_CLASS_CURTAIN, + "rts:DualCurtainRTSComponent": DEVICE_CLASS_CURTAIN, + "rts:ExteriorVenetianBlindRTSComponent": DEVICE_CLASS_BLIND, + "rts:RollerShutterRTSComponent": DEVICE_CLASS_SHUTTER, + "rts:VenetianBlindRTSComponent": DEVICE_CLASS_BLIND, +} + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Tahoma covers.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + devices = [] + for device in hass.data[TAHOMA_DOMAIN]["devices"]["cover"]: + devices.append(TahomaCover(device, controller)) + add_entities(devices, True) + + +class TahomaCover(TahomaDevice, CoverDevice): + """Representation a Tahoma Cover.""" + + def __init__(self, tahoma_device, controller): + """Initialize the device.""" + super().__init__(tahoma_device, controller) + + self._closure = 0 + # 100 equals open + self._position = 100 + self._closed = False + self._rssi_level = None + self._icon = None + # Can be 0 and bigger + self._lock_timer = 0 + self._lock_start_ts = None + self._lock_end_ts = None + # Can be 'comfortLevel1', 'comfortLevel2', 'comfortLevel3', + # 'comfortLevel4', 'environmentProtection', 'humanProtection', + # 'userLevel1', 'userLevel2' + self._lock_level = None + # Can be 'LSC', 'SAAC', 'SFC', 'UPS', 'externalGateway', 'localUser', + # 'myself', 'rain', 'security', 'temperature', 'timer', 'user', 'wind' + self._lock_originator = None + + def update(self): + """Update method.""" + self.controller.get_states([self.tahoma_device]) + + # For vertical covers + self._closure = self.tahoma_device.active_states.get("core:ClosureState") + # For horizontal covers + if self._closure is None: + self._closure = self.tahoma_device.active_states.get("core:DeploymentState") + + # For all, if available + if "core:PriorityLockTimerState" in self.tahoma_device.active_states: + old_lock_timer = self._lock_timer + self._lock_timer = self.tahoma_device.active_states[ + "core:PriorityLockTimerState" + ] + # Derive timestamps from _lock_timer, only if not already set or + # something has changed + if self._lock_timer > 0: + _LOGGER.debug("Update %s, lock_timer: %d", self._name, self._lock_timer) + if self._lock_start_ts is None: + self._lock_start_ts = utcnow() + if self._lock_end_ts is None or old_lock_timer != self._lock_timer: + self._lock_end_ts = utcnow() + timedelta(seconds=self._lock_timer) + else: + self._lock_start_ts = None + self._lock_end_ts = None + else: + self._lock_timer = 0 + self._lock_start_ts = None + self._lock_end_ts = None + + self._lock_level = self.tahoma_device.active_states.get( + "io:PriorityLockLevelState" + ) + + self._lock_originator = self.tahoma_device.active_states.get( + "io:PriorityLockOriginatorState" + ) + + self._rssi_level = self.tahoma_device.active_states.get("core:RSSILevelState") + + # Define which icon to use + if self._lock_timer > 0: + if self._lock_originator == "wind": + self._icon = "mdi:weather-windy" + else: + self._icon = "mdi:lock-alert" + else: + self._icon = None + + # Define current position. + # _position: 0 is closed, 100 is fully open. + # 'core:ClosureState': 100 is closed, 0 is fully open. + if self._closure is not None: + if self.tahoma_device.type == HORIZONTAL_AWNING: + self._position = self._closure + else: + self._position = 100 - self._closure + if self._position <= 5: + self._position = 0 + if self._position >= 95: + self._position = 100 + self._closed = self._position == 0 + else: + self._position = None + if "core:OpenClosedState" in self.tahoma_device.active_states: + self._closed = ( + self.tahoma_device.active_states["core:OpenClosedState"] == "closed" + ) + else: + self._closed = False + + _LOGGER.debug("Update %s, position: %d", self._name, self._position) + + @property + def current_cover_position(self): + """Return current position of cover.""" + return self._position + + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + if self.tahoma_device.type == "io:WindowOpenerVeluxIOComponent": + command = "setClosure" + else: + command = "setPosition" + + if self.tahoma_device.type == HORIZONTAL_AWNING: + self.apply_action(command, kwargs.get(ATTR_POSITION, 0)) + else: + self.apply_action(command, 100 - kwargs.get(ATTR_POSITION, 0)) + + @property + def is_closed(self): + """Return if the cover is closed.""" + return self._closed + + @property + def device_class(self): + """Return the class of the device.""" + return TAHOMA_DEVICE_CLASSES.get(self.tahoma_device.type) + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + super_attr = super().device_state_attributes + if super_attr is not None: + attr.update(super_attr) + + if "core:Memorized1PositionState" in self.tahoma_device.active_states: + attr[ATTR_MEM_POS] = self.tahoma_device.active_states[ + "core:Memorized1PositionState" + ] + if self._rssi_level is not None: + attr[ATTR_RSSI_LEVEL] = self._rssi_level + if self._lock_start_ts is not None: + attr[ATTR_LOCK_START_TS] = self._lock_start_ts.isoformat() + if self._lock_end_ts is not None: + attr[ATTR_LOCK_END_TS] = self._lock_end_ts.isoformat() + if self._lock_level is not None: + attr[ATTR_LOCK_LEVEL] = self._lock_level + if self._lock_originator is not None: + attr[ATTR_LOCK_ORIG] = self._lock_originator + return attr + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return self._icon + + def open_cover(self, **kwargs): + """Open the cover.""" + self.apply_action("open") + + def close_cover(self, **kwargs): + """Close the cover.""" + self.apply_action("close") + + def stop_cover(self, **kwargs): + """Stop the cover.""" + if ( + self.tahoma_device.type + == "io:RollerShutterWithLowSpeedManagementIOComponent" + ): + self.apply_action("setPosition", "secured") + elif self.tahoma_device.type in ( + "rts:BlindRTSComponent", + "io:ExteriorVenetianBlindIOComponent", + "rts:VenetianBlindRTSComponent", + "rts:DualCurtainRTSComponent", + "rts:ExteriorVenetianBlindRTSComponent", + "rts:BlindRTSComponent", + ): + self.apply_action("my") + elif self.tahoma_device.type in ( + HORIZONTAL_AWNING, + "io:RollerShutterGenericIOComponent", + "io:VerticalExteriorAwningIOComponent", + "io:VerticalInteriorBlindVeluxIOComponent", + "io:WindowOpenerVeluxIOComponent", + ): + self.apply_action("stop") + else: + self.apply_action("stopIdentify") diff --git a/homeassistant/components/tahoma/manifest.json b/homeassistant/components/tahoma/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..1e99d4b288d725f81b7b5ccc0ebaaa4a2822b91d --- /dev/null +++ b/homeassistant/components/tahoma/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "tahoma", + "name": "Tahoma", + "documentation": "https://www.home-assistant.io/integrations/tahoma", + "requirements": [ + "tahoma-api==0.0.14" + ], + "dependencies": [], + "codeowners": [ + "@philklei" + ] +} diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py new file mode 100644 index 0000000000000000000000000000000000000000..e54ff91a0f6e908f1dc1c82d6f2b05b8e8a3a0cc --- /dev/null +++ b/homeassistant/components/tahoma/scene.py @@ -0,0 +1,41 @@ +"""Support for Tahoma scenes.""" +import logging + +from homeassistant.components.scene import Scene + +from . import DOMAIN as TAHOMA_DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Tahoma scenes.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + scenes = [] + for scene in hass.data[TAHOMA_DOMAIN]["scenes"]: + scenes.append(TahomaScene(scene, controller)) + add_entities(scenes, True) + + +class TahomaScene(Scene): + """Representation of a Tahoma scene entity.""" + + def __init__(self, tahoma_scene, controller): + """Initialize the scene.""" + self.tahoma_scene = tahoma_scene + self.controller = controller + self._name = self.tahoma_scene.name + + def activate(self): + """Activate the scene.""" + self.controller.launch_action_group(self.tahoma_scene.oid) + + @property + def name(self): + """Return the name of the scene.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes of the scene.""" + return {"tahoma_scene_oid": self.tahoma_scene.oid} diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py new file mode 100644 index 0000000000000000000000000000000000000000..5279b160d9c5405d63831c7524efd832b4811622 --- /dev/null +++ b/homeassistant/components/tahoma/sensor.py @@ -0,0 +1,106 @@ +"""Support for Tahoma sensors.""" +from datetime import timedelta +import logging + +from homeassistant.const import ATTR_BATTERY_LEVEL +from homeassistant.helpers.entity import Entity + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=60) + +ATTR_RSSI_LEVEL = "rssi_level" + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up Tahoma controller devices.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + devices = [] + for device in hass.data[TAHOMA_DOMAIN]["devices"]["sensor"]: + devices.append(TahomaSensor(device, controller)) + add_entities(devices, True) + + +class TahomaSensor(TahomaDevice, Entity): + """Representation of a Tahoma Sensor.""" + + def __init__(self, tahoma_device, controller): + """Initialize the sensor.""" + self.current_value = None + self._available = False + super().__init__(tahoma_device, controller) + + @property + def state(self): + """Return the name of the sensor.""" + return self.current_value + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + if self.tahoma_device.type == "Temperature Sensor": + return None + if self.tahoma_device.type == "io:SomfyContactIOSystemSensor": + return None + if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": + return None + if self.tahoma_device.type == "io:LightIOSystemSensor": + return "lx" + if self.tahoma_device.type == "Humidity Sensor": + return "%" + if self.tahoma_device.type == "rtds:RTDSContactSensor": + return None + if self.tahoma_device.type == "rtds:RTDSMotionSensor": + return None + + def update(self): + """Update the state.""" + self.controller.get_states([self.tahoma_device]) + if self.tahoma_device.type == "io:LightIOSystemSensor": + self.current_value = self.tahoma_device.active_states["core:LuminanceState"] + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + if self.tahoma_device.type == "io:SomfyContactIOSystemSensor": + self.current_value = self.tahoma_device.active_states["core:ContactState"] + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": + self.current_value = self.tahoma_device.active_states["core:ContactState"] + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + if self.tahoma_device.type == "rtds:RTDSContactSensor": + self.current_value = self.tahoma_device.active_states["core:ContactState"] + self._available = True + if self.tahoma_device.type == "rtds:RTDSMotionSensor": + self.current_value = self.tahoma_device.active_states["core:OccupancyState"] + self._available = True + + _LOGGER.debug("Update %s, value: %d", self._name, self.current_value) + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + super_attr = super().device_state_attributes + if super_attr is not None: + attr.update(super_attr) + + if "core:RSSILevelState" in self.tahoma_device.active_states: + attr[ATTR_RSSI_LEVEL] = self.tahoma_device.active_states[ + "core:RSSILevelState" + ] + if "core:SensorDefectState" in self.tahoma_device.active_states: + attr[ATTR_BATTERY_LEVEL] = self.tahoma_device.active_states[ + "core:SensorDefectState" + ] + return attr + + @property + def available(self): + """Return True if entity is available.""" + return self._available diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py new file mode 100644 index 0000000000000000000000000000000000000000..a0a95ab47ce05632137c576282ce87ea9cf6b06b --- /dev/null +++ b/homeassistant/components/tahoma/switch.py @@ -0,0 +1,109 @@ +"""Support for Tahoma switches.""" +import logging + +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import STATE_OFF, STATE_ON + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice + +_LOGGER = logging.getLogger(__name__) + +ATTR_RSSI_LEVEL = "rssi_level" + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up Tahoma switches.""" + controller = hass.data[TAHOMA_DOMAIN]["controller"] + devices = [] + for switch in hass.data[TAHOMA_DOMAIN]["devices"]["switch"]: + devices.append(TahomaSwitch(switch, controller)) + add_entities(devices, True) + + +class TahomaSwitch(TahomaDevice, SwitchDevice): + """Representation a Tahoma Switch.""" + + def __init__(self, tahoma_device, controller): + """Initialize the switch.""" + super().__init__(tahoma_device, controller) + self._state = STATE_OFF + self._skip_update = False + self._available = False + + def update(self): + """Update method.""" + # Postpone the immediate state check for changes that take time. + if self._skip_update: + self._skip_update = False + return + + self.controller.get_states([self.tahoma_device]) + + if self.tahoma_device.type == "io:OnOffLightIOComponent": + if self.tahoma_device.active_states.get("core:OnOffState") == "on": + self._state = STATE_ON + else: + self._state = STATE_OFF + + self._available = bool( + self.tahoma_device.active_states.get("core:StatusState") == "available" + ) + + _LOGGER.debug("Update %s, state: %s", self._name, self._state) + + @property + def device_class(self): + """Return the class of the device.""" + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + return "garage" + return None + + def turn_on(self, **kwargs): + """Send the on command.""" + _LOGGER.debug("Turn on: %s", self._name) + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + self.toggle() + else: + self.apply_action("on") + self._skip_update = True + self._state = STATE_ON + + def turn_off(self, **kwargs): + """Send the off command.""" + _LOGGER.debug("Turn off: %s", self._name) + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + return + + self.apply_action("off") + self._skip_update = True + self._state = STATE_OFF + + def toggle(self, **kwargs): + """Click the switch.""" + self.apply_action("cycle") + + @property + def is_on(self): + """Get whether the switch is in on state.""" + if self.tahoma_device.type == "rts:GarageDoor4TRTSComponent": + return False + return bool(self._state == STATE_ON) + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + super_attr = super().device_state_attributes + if super_attr is not None: + attr.update(super_attr) + + if "core:RSSILevelState" in self.tahoma_device.active_states: + attr[ATTR_RSSI_LEVEL] = self.tahoma_device.active_states[ + "core:RSSILevelState" + ] + return attr + + @property + def available(self): + """Return True if entity is available.""" + return self._available diff --git a/requirements_all.txt b/requirements_all.txt index 5f6e9ac78aab2c908385189c26320898e6dda3d8..c91ffeace3f6291b2bb3e16113ba3b3aee299260 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1906,6 +1906,9 @@ swisshydrodata==0.0.3 # homeassistant.components.synology_srm synology-srm==0.0.7 +# homeassistant.components.tahoma +tahoma-api==0.0.14 + # homeassistant.components.tank_utility tank_utility==1.4.0