Support for animation (so far color transition and blink) on Philips Hue plugin

This commit is contained in:
Fabio Manganiello 2018-06-16 04:10:54 +02:00
parent 2d6994c057
commit 803aa68f11

View file

@ -1,6 +1,12 @@
import random
import time
from enum import Enum
from threading import Thread
from redis import Redis
from redis.exceptions import TimeoutError as QueueTimeoutError
from phue import Bridge
from platypush.message.response import Response
from .. import LightPlugin
@ -11,6 +17,17 @@ class LightHuePlugin(LightPlugin):
MAX_BRI = 255
MAX_SAT = 255
MAX_HUE = 65535
ANIMATION_CTRL_QUEUE_NAME = 'platypush/light/hue/AnimationCtrl'
class Animation(Enum):
COLOR_TRANSITION = 'color_transition'
BLINK = 'blink'
def __eq__(self, other):
if isinstance(other, str):
return self.value == other
elif isinstance(other, self.__class__):
return self == other
def __init__(self, bridge, lights=None, groups=None):
"""
@ -39,6 +56,8 @@ class LightHuePlugin(LightPlugin):
else:
self.lights = [l.name for l in self.bridge.lights]
self.redis = None
self.animation_thread = None
self.logger.info('Configured lights: "{}"'. format(self.lights))
def _expand_groups(self):
@ -85,22 +104,22 @@ class LightHuePlugin(LightPlugin):
lights = []; groups = []
if 'lights' in kwargs and kwargs['lights']:
lights = kwargs['lights'].split(',') \
if isinstance(lights, str) else kwargs['lights']
lights = kwargs.pop('lights').split(',').strip() \
if isinstance(lights, str) else kwargs.pop('lights')
elif 'groups' in kwargs and kwargs['groups']:
groups = kwargs['groups'].split(',') \
if isinstance(groups, str) else kwargs['groups']
groups = kwargs.pop('groups').split(',').strip() \
if isinstance(groups, str) else kwargs.pop('groups')
else:
lights = self.lights
groups = self.groups
try:
if attr == 'scene':
self.bridge.run_scene(groups[0], kwargs['name'])
self.bridge.run_scene(groups[0], kwargs.pop('name'))
elif groups:
self.bridge.set_group(groups, attr, *args)
self.bridge.set_group(groups, attr, *args, **kwargs)
elif lights:
self.bridge.set_light(lights, attr, *args)
self.bridge.set_light(lights, attr, *args, **kwargs)
except Exception as e:
# Reset bridge connection
self.bridge = None
@ -108,6 +127,16 @@ class LightHuePlugin(LightPlugin):
return Response(output='ok')
def set_light(self, light, **kwargs):
self.connect()
self.bridge.set_light(light, **kwargs)
return Response(output='ok')
def set_group(self, group, **kwargs):
self.connect()
self.bridge.set_group(group, **kwargs)
return Response(output='ok')
def on(self, lights=[], groups=[]):
return self._exec('on', True, lights=lights, groups=groups)
@ -129,6 +158,121 @@ class LightHuePlugin(LightPlugin):
def scene(self, name, lights=[], groups=[]):
return self._exec('scene', name=name, lights=lights, groups=groups)
def stop_animation(self):
if not self.redis:
self.logger.info('No animation is currently running')
return
self.redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP')
def animate(self, animation, duration=None,
hue_range=[0, MAX_HUE], sat_range=[0, MAX_SAT],
bri_range=[MAX_BRI-1, MAX_BRI], lights=None, groups=None,
hue_step=1000, sat_step=2, bri_step=1, transition_seconds=1.0):
def _initialize_light_attrs(lights):
if animation == self.Animation.COLOR_TRANSITION:
return { l: {
'hue': random.randint(hue_range[0], hue_range[1]),
'sat': random.randint(sat_range[0], sat_range[1]),
'bri': random.randint(bri_range[0], bri_range[1]),
} for l in lights }
elif animation == self.Animation.BLINK:
return { l: {
'on': True,
'bri': self.MAX_BRI,
'transitiontime': 0,
} for l in lights }
def _next_light_attrs(lights):
if animation == self.Animation.COLOR_TRANSITION:
for (light, attrs) in lights.items():
for (attr, value) in attrs.items():
if attr == 'hue':
attr_range = hue_range
attr_step = hue_step
elif attr == 'bri':
attr_range = bri_range
attr_step = bri_step
elif attr == 'sat':
attr_range = sat_range
attr_step = sat_step
lights[light][attr] = ((value - attr_range[0] + attr_step) %
(attr_range[1]-attr_range[0]+1)) + \
attr_range[0]
elif animation == self.Animation.BLINK:
lights = { light: {
'on': False if attrs['on'] else True,
'bri': self.MAX_BRI,
'transitiontime': 0,
} for (light, attrs) in lights.items() }
return lights
def _should_stop():
try:
self.redis.blpop(self.ANIMATION_CTRL_QUEUE_NAME)
return True
except QueueTimeoutError:
return False
def _animate_thread(lights):
self.logger.info('Starting {} animation'.format(
animation, (lights or groups)))
lights = _initialize_light_attrs(lights)
animation_start_time = time.time()
stop_animation = False
while True:
if stop_animation or \
(duration and time.time() - animation_start_time > duration):
break
if animation == self.Animation.COLOR_TRANSITION:
for (light, attrs) in lights.items():
self.logger.info('Setting {} to {}'.format(light, attrs))
self.bridge.set_light(light, attrs)
stop_animation = _should_stop()
if stop_animation: break
elif animation == self.Animation.BLINK:
conf = lights[list(lights.keys())[0]]
self.logger.info('Setting lights to {}'.format(conf))
if groups:
self.bridge.set_group([g.name for f in groups], conf)
else:
self.bridge.set_light(lights.keys(), conf)
stop_animation = _should_stop()
if stop_animation: break
lights = _next_light_attrs(lights)
self.logger.info('Stopping animation')
self.animation_thread = None
self.redis = None
self.redis = Redis(socket_timeout=transition_seconds)
if groups:
groups = [g for g in self.bridge.groups if g.name in groups]
lights = lights or []
for g in groups:
lights.extend([l.name for l in g.lights])
elif not lights:
lights = self.lights
if self.animation_thread and self.animation_thread.is_alive():
self.stop_animation()
self.animation_thread = Thread(target=_animate_thread, args=(lights,))
self.animation_thread.start()
return Response(output='ok')
# vim:sw=4:ts=4:et: