From 8fc015b3e1db6c96383138c00ad41b5102a0750f Mon Sep 17 00:00:00 2001 From: Vassilis Panos Date: Mon, 26 Aug 2019 02:53:43 +0300 Subject: [PATCH] Initial release --- custom_components/demotez/__init__.py | 98 +++++++++++++++++++ custom_components/demotez/base_remote.py | 71 ++++++++++++++ custom_components/demotez/const.py | 5 + custom_components/demotez/manifest.json | 8 ++ custom_components/demotez/philips_remote.py | 80 +++++++++++++++ custom_components/demotez/tradfri_dimmer.py | 29 ++++++ custom_components/demotez/tradfri_remote.py | 103 ++++++++++++++++++++ 7 files changed, 394 insertions(+) create mode 100644 custom_components/demotez/__init__.py create mode 100644 custom_components/demotez/base_remote.py create mode 100644 custom_components/demotez/const.py create mode 100644 custom_components/demotez/manifest.json create mode 100644 custom_components/demotez/philips_remote.py create mode 100644 custom_components/demotez/tradfri_dimmer.py create mode 100644 custom_components/demotez/tradfri_remote.py diff --git a/custom_components/demotez/__init__.py b/custom_components/demotez/__init__.py new file mode 100644 index 0000000..5865c63 --- /dev/null +++ b/custom_components/demotez/__init__.py @@ -0,0 +1,98 @@ +import logging +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from .const import * +from .tradfri_remote import TradfriRemote +from .tradfri_dimmer import TradfriDimmer +from .philips_remote import PhilipsRemote + +_LOGGER = logging.getLogger(__name__) + +REMOTE_TYPES = [ + TRADFRI_REMOTE, + TRADFRI_DIMMER, + PHILIPS_REMOTE +] + +# Tradfri remotes +REMOTE_TOGGLE_SCHEMA = vol.Schema({ + vol.Optional('sync_lights', default=True): cv.boolean, + vol.Optional('transition'): cv.positive_int, + vol.Required('entities'): cv.entity_ids +}) + +# Tradfri dimmers, Philips remotes +REMOTE_TURN_ON_SCHEMA = vol.Schema({ + vol.Optional('transition'): cv.positive_int, + vol.Required('entities'): cv.entity_ids +}) + +# Tradfri dimmers, Philips remotes +REMOTE_TURN_OFF_SCHEMA = vol.Schema({ + vol.Optional('transition'): cv.positive_int, + vol.Required('entities'): cv.entity_ids +}) + +# Philips remotes +REMOTE_LONG_TURN_ON_SCHEMA = vol.Schema({ + vol.Optional('transition'): cv.positive_int, + vol.Required('entities'): cv.entity_ids +}) + +# Philips remotes +REMOTE_LONG_TURN_OFF_SCHEMA = vol.Schema({ + vol.Optional('transition'): cv.positive_int, + vol.Required('entities'): cv.entity_ids +}) + +# Tradfri remotes, Trandfi dimmers, Philips remotes +REMOTE_DIMMING_SCHEMA = vol.Schema({ + vol.Optional('step', default=30): cv.positive_int, + vol.Optional('transition'): cv.positive_int, + vol.Optional('long_press_full_brightness', default=False): cv.boolean, + vol.Optional('long_press_turn_off', default=False): cv.boolean, + vol.Required('entities'): cv.entity_ids +}) + +REMOTE_SCHEMA = vol.Schema({ + vol.Required('type'): vol.In(REMOTE_TYPES), + vol.Required('id'): cv.string, + vol.Optional('toggle'): REMOTE_TOGGLE_SCHEMA, + vol.Optional('turn_on'): REMOTE_TURN_ON_SCHEMA, + vol.Optional('turn_off'): REMOTE_TURN_OFF_SCHEMA, + vol.Optional('long_turn_on'): REMOTE_LONG_TURN_ON_SCHEMA, + vol.Optional('long_turn_off'): REMOTE_LONG_TURN_OFF_SCHEMA, + vol.Optional('dimming'): REMOTE_DIMMING_SCHEMA +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required('remotes'): vol.All(cv.ensure_list, [REMOTE_SCHEMA]) + }) +}, extra=vol.ALLOW_EXTRA) + +async def async_setup(hass, config): + conf = config.get(DOMAIN) + remotes = [] + + for remote_config in conf.get('remotes'): + remote_type = remote_config.get('type') + + if remote_type == TRADFRI_REMOTE: + remotes.append(TradfriRemote(hass, remote_config)) + + if remote_type == TRADFRI_DIMMER: + remotes.append(TradfriDimmer(hass, remote_config)) + + if remote_type == PHILIPS_REMOTE: + remotes.append(PhilipsRemote(hass, remote_config)) + + async def dispatch_deconz_events(event): + async_dispatcher_send(hass, DECONZ_EVENT, event) + + hass.bus.async_listen(DECONZ_EVENT, dispatch_deconz_events) + + return True \ No newline at end of file diff --git a/custom_components/demotez/base_remote.py b/custom_components/demotez/base_remote.py new file mode 100644 index 0000000..dc3c059 --- /dev/null +++ b/custom_components/demotez/base_remote.py @@ -0,0 +1,71 @@ +import asyncio +import logging +import time + +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DECONZ_EVENT + +_LOGGER = logging.getLogger(__name__) + +class Remote: + def __init__(self, hass, remote_config): + self.hass = hass + self.remote_config = remote_config + + async_dispatcher_connect( + self.hass, DECONZ_EVENT, self.handle_events) + + async def handle_events(self, event): + device_id = event.data.get('id') + event_id = event.data.get('event') + + if not device_id == self.remote_config.get('id'): + return + + await self.event_received(event_id) + + async def event_received(self, event_id): + raise NotImplementedError() + + async def short_dimming(self, event_id): + dimming_config = self.remote_config.get('dimming') + step = dimming_config.get('step') + transition = dimming_config.get('transition') + entities = dimming_config.get('entities') + min_brightness = 1 + max_brightness = 255 + + for entity_id in entities: + brightness = self.hass.states.get(entity_id).attributes.get('brightness') + + # If the brightness is none (light turned off?), turn on the light + if brightness and event_id == 2002: + brightness += step + + if brightness > max_brightness: + brightness = max_brightness + + if brightness and event_id == 3002: + brightness -= step + + if brightness < min_brightness: + brightness = min_brightness + + await self.set_brightness(entity_id, brightness, transition) + + async def set_brightness(self, entity_id, brightness, transition): + service_data = {'entity_id': entity_id} + + if not brightness is None: + service_data['brightness'] = brightness + + if not transition is None: + service_data['transition'] = transition + + await self.hass.services.async_call('light', 'turn_on', service_data) + + def is_any_on(self, entities): + for entity_id in entities: + if self.hass.states.get(entity_id).state == 'on': + return True \ No newline at end of file diff --git a/custom_components/demotez/const.py b/custom_components/demotez/const.py new file mode 100644 index 0000000..9bd4fa0 --- /dev/null +++ b/custom_components/demotez/const.py @@ -0,0 +1,5 @@ +DOMAIN = 'demotez' +DECONZ_EVENT = 'deconz_event' +TRADFRI_REMOTE = 'tradfri_remote' +TRADFRI_DIMMER = 'tradfri_dimmer' +PHILIPS_REMOTE = 'philips_remote' \ No newline at end of file diff --git a/custom_components/demotez/manifest.json b/custom_components/demotez/manifest.json new file mode 100644 index 0000000..9780118 --- /dev/null +++ b/custom_components/demotez/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "demotez", + "name": "deMotez", + "documentation": "https://github.com/smartHomeHub/deMotez", + "dependencies": [], + "codeowners": ["@smartHomeHub"], + "requirements": [] + } \ No newline at end of file diff --git a/custom_components/demotez/philips_remote.py b/custom_components/demotez/philips_remote.py new file mode 100644 index 0000000..9f349aa --- /dev/null +++ b/custom_components/demotez/philips_remote.py @@ -0,0 +1,80 @@ +import logging +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .base_remote import Remote + +_LOGGER = logging.getLogger(__name__) + +class PhilipsRemote(Remote): + async def event_received(self, event_id): + if event_id == 1002: + await self.turn_on_off('turn_on') + + if event_id == 4002: + await self.turn_on_off('turn_off') + + if event_id == 1001: + await self.turn_on_off('turn_on', True) + + if event_id == 4001: + await self.turn_on_off('turn_off', True) + + if event_id in [2002, 3002]: + await self.short_dimming(event_id) + + if event_id in [2001, 3001]: + await self.long_dimming(event_id) + + async def turn_on_off(self, action, long = False): + config = self.remote_config.get( + 'long_' + action if long is True else action + ) + transition = config.get('transition') + entities = config.get('entities') + + for entity_id in entities: + service_data = {'entity_id': entity_id} + + if transition: + service_data['transition'] = transition + + await self.hass.services.async_call('homeassistant', action, service_data) + + async def long_dimming(self, event_id): + dimming_config = self.remote_config.get('dimming') + step = dimming_config.get('step') + transition = dimming_config.get('transition') + long_press_full_brightness = dimming_config.get('long_press_full_brightness') + long_press_turn_off = dimming_config.get('long_press_turn_off') + entities = dimming_config.get('entities') + min_brightness = 1 + max_brightness = 255 + + if event_id == 2001: + for entity_id in entities: + if long_press_full_brightness: + brightness = max_brightness + else: + brightness = self.hass.states.get(entity_id).attributes.get('brightness') + + if brightness: + brightness += step + + if brightness > max_brightness: + brightness = max_brightness + + await self.set_brightness(entity_id, brightness, transition) + + if event_id == 3001: + for entity_id in entities: + if long_press_turn_off: + brightness = 0 + else: + brightness = self.hass.states.get(entity_id).attributes.get('brightness') + + if brightness: + brightness -= step + + if brightness < min_brightness: + brightness = min_brightness + + await self.set_brightness(entity_id, brightness, transition) \ No newline at end of file diff --git a/custom_components/demotez/tradfri_dimmer.py b/custom_components/demotez/tradfri_dimmer.py new file mode 100644 index 0000000..e80014a --- /dev/null +++ b/custom_components/demotez/tradfri_dimmer.py @@ -0,0 +1,29 @@ +import logging +from datetime import timedelta +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util import Throttle +from .base_remote import Remote + +_LOGGER = logging.getLogger(__name__) + +class TradfriDimmer(Remote): + @Throttle(timedelta(seconds=0.5)) + async def event_received(self, event_id): + if event_id == 4002: + await self.turn_off() + + if event_id in [2002, 3002]: + await self.short_dimming(event_id) + + async def turn_off(self): + config = self.remote_config.get('dimming') + transition = config.get('transition') + entities = config.get('entities') + + for entity_id in entities: + service_data = {'entity_id': entity_id} + + if transition: + service_data['transition'] = transition + + await self.hass.services.async_call('light', 'turn_off', service_data) \ No newline at end of file diff --git a/custom_components/demotez/tradfri_remote.py b/custom_components/demotez/tradfri_remote.py new file mode 100644 index 0000000..72a2742 --- /dev/null +++ b/custom_components/demotez/tradfri_remote.py @@ -0,0 +1,103 @@ +import asyncio +import logging +import time +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .base_remote import Remote + +_LOGGER = logging.getLogger(__name__) + +class TradfriRemote(Remote): + async def event_received(self, event_id): + if event_id == 1002: + await self.toggle() + + if event_id in [2002, 3002]: + await self.short_dimming(event_id) + + if event_id in [2001, 3001]: + await self.long_dimming(event_id) + + if event_id in [2003, 3003]: + self._long_dim_released = True + + async def toggle(self): + toggle_config = self.remote_config.get('toggle') + sync_lights = toggle_config.get('sync_lights') + transition = toggle_config.get('transition') + entities = toggle_config.get('entities') + action = 'toggle' + + if sync_lights is True: + action = 'turn_off' if self.is_any_on(entities) else 'turn_on' + + for entity_id in entities: + service_data = {'entity_id': entity_id} + + if transition: + service_data['transition'] = transition + + await self.hass.services.async_call('homeassistant', action, service_data) + + async def long_dimming(self, event_id): + dimming_config = self.remote_config.get('dimming') + step = 30 + transition = dimming_config.get('transition') + long_press_full_brightness = dimming_config.get('long_press_full_brightness') + long_press_turn_off = dimming_config.get('long_press_turn_off') + entities = dimming_config.get('entities') + min_brightness = 1 + max_brightness = 255 + + if long_press_full_brightness and event_id == 2001: + for entity_id in entities: + await self.set_brightness(entity_id, max_brightness, transition) + return + + if long_press_turn_off and event_id == 3001: + for entity_id in entities: + await self.set_brightness(entity_id, 0, transition) + return + + if event_id == 2001: + self._long_dim_released = False + + while self._long_dim_released == False: + max_bright = dict.fromkeys(entities, False) + + for entity_id in entities: + brightness = self.hass.states.get(entity_id).attributes.get('brightness') + + if brightness: + brightness += step + + if brightness > max_brightness: + brightness = max_brightness + max_bright[entity_id] = True + + await self.set_brightness(entity_id, brightness, None) + await asyncio.sleep(0.1) + + if all(x is True for x in max_bright.values()): + self._long_dim_released = True + + if event_id == 3001: + self._long_dim_released = False + + while self._long_dim_released == False: + max_bright = dict.fromkeys(entities, False) + + for entity_id in entities: + brightness = self.hass.states.get(entity_id).attributes.get('brightness') + + if brightness: + brightness -= step + + if brightness < min_brightness: + brightness = min_brightness + max_bright[entity_id] = True + + await self.set_brightness(entity_id, brightness, None) + await asyncio.sleep(0.1) + + if all(x is True for x in max_bright.values()): + self._long_dim_released = True \ No newline at end of file