Use a threading Event to synchronize with the Hue animation thread instead of relying on the Redis backend

This commit is contained in:
Fabio Manganiello 2020-10-14 00:18:23 +02:00
parent 6d7f1502ce
commit 77530b4a06

View file

@ -3,11 +3,8 @@ import statistics
import time import time
from enum import Enum from enum import Enum
from threading import Thread from threading import Thread, Event
from redis import Redis
from redis.exceptions import TimeoutError as QueueTimeoutError
from platypush.context import get_backend
from platypush.plugins import action from platypush.plugins import action
from platypush.plugins.light import LightPlugin from platypush.plugins.light import LightPlugin
from platypush.utils import set_thread_name from platypush.utils import set_thread_name
@ -58,7 +55,8 @@ class LightHuePlugin(LightPlugin):
self.logger.info('Initializing Hue lights plugin - bridge: "{}"'.format(self.bridge_address)) self.logger.info('Initializing Hue lights plugin - bridge: "{}"'.format(self.bridge_address))
self.connect() self.connect()
self.lights = []; self.groups = [] self.lights = []
self.groups = []
if lights: if lights:
self.lights = lights self.lights = lights
@ -66,19 +64,20 @@ class LightHuePlugin(LightPlugin):
self.groups = groups self.groups = groups
self._expand_groups() self._expand_groups()
else: else:
self.lights = [l.name for l in self.bridge.lights] # noinspection PyUnresolvedReferences
self.lights = [light.name for light in self.bridge.lights]
self.redis = None
self.animation_thread = None self.animation_thread = None
self.animations = {} self.animations = {}
self._animation_stop = Event()
self._init_animations() self._init_animations()
self.logger.info('Configured lights: "{}"'. format(self.lights)) self.logger.info('Configured lights: "{}"'.format(self.lights))
def _expand_groups(self): def _expand_groups(self):
groups = [g for g in self.bridge.groups if g.name in self.groups] groups = [g for g in self.bridge.groups if g.name in self.groups]
for g in groups: for group in groups:
for l in g.lights: for light in group.lights:
self.lights += [l.name] self.lights += [light.name]
def _init_animations(self): def _init_animations(self):
self.animations = { self.animations = {
@ -86,10 +85,10 @@ class LightHuePlugin(LightPlugin):
'lights': {}, 'lights': {},
} }
for g in self.bridge.groups: for group in self.bridge.groups:
self.animations['groups'][g.group_id] = None self.animations['groups'][group.group_id] = None
for l in self.bridge.lights: for light in self.bridge.lights:
self.animations['lights'][l.light_id] = None self.animations['lights'][light.light_id] = None
@action @action
def connect(self): def connect(self):
@ -295,7 +294,9 @@ class LightHuePlugin(LightPlugin):
self.bridge = None self.bridge = None
raise e raise e
lights = []; groups = [] lights = []
groups = []
if 'lights' in kwargs: if 'lights' in kwargs:
lights = kwargs.pop('lights').split(',').strip() \ lights = kwargs.pop('lights').split(',').strip() \
if isinstance(lights, str) else kwargs.pop('lights') if isinstance(lights, str) else kwargs.pop('lights')
@ -471,7 +472,7 @@ class LightHuePlugin(LightPlugin):
groups = [] groups = []
if lights is None: if lights is None:
lights = [] lights = []
return self._exec('bri', int(value) % (self.MAX_BRI+1), return self._exec('bri', int(value) % (self.MAX_BRI + 1),
lights=lights, groups=groups, **kwargs) lights=lights, groups=groups, **kwargs)
@action @action
@ -488,7 +489,7 @@ class LightHuePlugin(LightPlugin):
groups = [] groups = []
if lights is None: if lights is None:
lights = [] lights = []
return self._exec('sat', int(value) % (self.MAX_SAT+1), return self._exec('sat', int(value) % (self.MAX_SAT + 1),
lights=lights, groups=groups, **kwargs) lights=lights, groups=groups, **kwargs)
@action @action
@ -505,7 +506,7 @@ class LightHuePlugin(LightPlugin):
groups = [] groups = []
if lights is None: if lights is None:
lights = [] lights = []
return self._exec('hue', int(value) % (self.MAX_HUE+1), return self._exec('hue', int(value) % (self.MAX_HUE + 1),
lights=lights, groups=groups, **kwargs) lights=lights, groups=groups, **kwargs)
@action @action
@ -515,6 +516,8 @@ class LightHuePlugin(LightPlugin):
:param value: xY value :param value: xY value
:type value: list[float] containing the two values :type value: list[float] containing the two values
:param lights: List of lights.
:param groups: List of groups.
""" """
if groups is None: if groups is None:
groups = [] groups = []
@ -529,6 +532,8 @@ class LightHuePlugin(LightPlugin):
:param value: Temperature value (range: 0-255) :param value: Temperature value (range: 0-255)
:type value: int :type value: int
:param lights: List of lights.
:param groups: List of groups.
""" """
if groups is None: if groups is None:
groups = [] groups = []
@ -550,7 +555,6 @@ class LightHuePlugin(LightPlugin):
groups = [] groups = []
if lights is None: if lights is None:
lights = [] lights = []
bri = 0
if lights: if lights:
bri = statistics.mean([ bri = statistics.mean([
@ -571,10 +575,10 @@ class LightHuePlugin(LightPlugin):
if light['name'] in self.lights if light['name'] in self.lights
]) ])
delta *= (self.MAX_BRI/100) delta *= (self.MAX_BRI / 100)
if bri+delta < 0: if bri + delta < 0:
bri = 0 bri = 0
elif bri+delta > self.MAX_BRI: elif bri + delta > self.MAX_BRI:
bri = self.MAX_BRI bri = self.MAX_BRI
else: else:
bri += delta bri += delta
@ -595,7 +599,6 @@ class LightHuePlugin(LightPlugin):
groups = [] groups = []
if lights is None: if lights is None:
lights = [] lights = []
sat = 0
if lights: if lights:
sat = statistics.mean([ sat = statistics.mean([
@ -616,10 +619,10 @@ class LightHuePlugin(LightPlugin):
if light['name'] in self.lights if light['name'] in self.lights
]) ])
delta *= (self.MAX_SAT/100) delta *= (self.MAX_SAT / 100)
if sat+delta < 0: if sat + delta < 0:
sat = 0 sat = 0
elif sat+delta > self.MAX_SAT: elif sat + delta > self.MAX_SAT:
sat = self.MAX_SAT sat = self.MAX_SAT
else: else:
sat += delta sat += delta
@ -640,7 +643,6 @@ class LightHuePlugin(LightPlugin):
groups = [] groups = []
if lights is None: if lights is None:
lights = [] lights = []
hue = 0
if lights: if lights:
hue = statistics.mean([ hue = statistics.mean([
@ -661,10 +663,10 @@ class LightHuePlugin(LightPlugin):
if light['name'] in self.lights if light['name'] in self.lights
]) ])
delta *= (self.MAX_HUE/100) delta *= (self.MAX_HUE / 100)
if hue+delta < 0: if hue + delta < 0:
hue = 0 hue = 0
elif hue+delta > self.MAX_HUE: elif hue + delta > self.MAX_HUE:
hue = self.MAX_HUE hue = self.MAX_HUE
else: else:
hue += delta hue += delta
@ -693,17 +695,15 @@ class LightHuePlugin(LightPlugin):
:returns: True if there is an animation running, false otherwise. :returns: True if there is an animation running, false otherwise.
""" """
return self.animation_thread is not None return self.animation_thread is not None and self.animation_thread.is_alive()
@action @action
def stop_animation(self): def stop_animation(self):
""" """
Stop a running animation if any Stop a running animation.
""" """
if self.is_animation_running():
if self.animation_thread and self.animation_thread.is_alive(): self._animation_stop.set()
redis = self._get_redis()
redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP')
self._init_animations() self._init_animations()
@action @action
@ -756,10 +756,10 @@ class LightHuePlugin(LightPlugin):
if groups: if groups:
groups = [g for g in self.bridge.groups if g.name in groups or g.group_id in groups] groups = [g for g in self.bridge.groups if g.name in groups or g.group_id in groups]
lights = lights or [] lights = lights or []
for g in groups: for group in groups:
lights.extend([l.name for l in g.lights]) lights.extend([light.name for light in group.lights])
elif lights: elif lights:
lights = [l.name for l in self.bridge.lights if l.name in lights or l.light_id in lights] lights = [light.name for light in self.bridge.lights if light.name in lights or light.light_id in lights]
else: else:
lights = self.lights lights = self.lights
@ -776,32 +776,32 @@ class LightHuePlugin(LightPlugin):
} }
if groups: if groups:
for g in groups: for group in groups:
self.animations['groups'][g.group_id] = info self.animations['groups'][group.group_id] = info
for l in self.bridge.lights: for light in self.bridge.lights:
if l.name in lights: if light.name in lights:
self.animations['lights'][l.light_id] = info self.animations['lights'][light.light_id] = info
def _initialize_light_attrs(lights): def _initialize_light_attrs(lights):
if animation == self.Animation.COLOR_TRANSITION: if animation == self.Animation.COLOR_TRANSITION:
return { l: { return {light: {
'hue': random.randint(hue_range[0], hue_range[1]), 'hue': random.randint(hue_range[0], hue_range[1]),
'sat': random.randint(sat_range[0], sat_range[1]), 'sat': random.randint(sat_range[0], sat_range[1]),
'bri': random.randint(bri_range[0], bri_range[1]), 'bri': random.randint(bri_range[0], bri_range[1]),
} for l in lights } } for light in lights}
elif animation == self.Animation.BLINK: elif animation == self.Animation.BLINK:
return { l: { return {light: {
'on': True, 'on': True,
'bri': self.MAX_BRI, 'bri': self.MAX_BRI,
'transitiontime': 0, 'transitiontime': 0,
} for l in lights } } for light in lights}
def _next_light_attrs(lights): def _next_light_attrs(lights):
if animation == self.Animation.COLOR_TRANSITION: if animation == self.Animation.COLOR_TRANSITION:
for (light, attrs) in lights.items(): for (light, attrs) in lights.items():
for (attr, value) in attrs.items(): for (attr, value) in attrs.items():
attr_range = [0,0] attr_range = [0, 0]
attr_step = 0 attr_step = 0
if attr == 'hue': if attr == 'hue':
@ -815,24 +815,19 @@ class LightHuePlugin(LightPlugin):
attr_step = sat_step attr_step = sat_step
lights[light][attr] = ((value - attr_range[0] + attr_step) % lights[light][attr] = ((value - attr_range[0] + attr_step) %
(attr_range[1]-attr_range[0]+1)) + \ (attr_range[1] - attr_range[0] + 1)) + \
attr_range[0] attr_range[0]
elif animation == self.Animation.BLINK: elif animation == self.Animation.BLINK:
lights = { light: { lights = {light: {
'on': False if attrs['on'] else True, 'on': False if attrs['on'] else True,
'bri': self.MAX_BRI, 'bri': self.MAX_BRI,
'transitiontime': 0, 'transitiontime': 0,
} for (light, attrs) in lights.items() } } for (light, attrs) in lights.items()}
return lights return lights
def _should_stop(): def _should_stop():
try: return self._animation_stop.is_set()
redis = self._get_redis(transition_seconds)
redis.blpop(self.ANIMATION_CTRL_QUEUE_NAME)
return True
except QueueTimeoutError:
return False
def _animate_thread(lights): def _animate_thread(lights):
set_thread_name('HueAnimate') set_thread_name('HueAnimate')
@ -874,24 +869,16 @@ class LightHuePlugin(LightPlugin):
self.logger.info('Stopping animation') self.logger.info('Stopping animation')
self.animation_thread = None self.animation_thread = None
self.redis = None
self.stop_animation() self.stop_animation()
self._animation_stop.clear()
self.animation_thread = Thread(target=_animate_thread, self.animation_thread = Thread(target=_animate_thread,
name='HueAnimate', name='HueAnimate',
args=(lights,)) args=(lights,))
self.animation_thread.start() self.animation_thread.start()
def _get_redis(self, socket_timeout=1.0):
if not self.redis:
redis_args = get_backend('redis').redis_args
redis_args['socket_timeout'] = socket_timeout
self.redis = Redis(**redis_args)
return self.redis
def status(self): def status(self):
# TODO # TODO
pass pass
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: