forked from platypush/platypush
947 lines
30 KiB
Python
947 lines
30 KiB
Python
import random
|
|
import statistics
|
|
import time
|
|
|
|
from enum import Enum
|
|
from threading import Thread, Event
|
|
from typing import List
|
|
|
|
from platypush.context import get_bus
|
|
from platypush.message.event.light import LightAnimationStartedEvent, LightAnimationStoppedEvent
|
|
from platypush.plugins import action
|
|
from platypush.plugins.light import LightPlugin
|
|
from platypush.utils import set_thread_name
|
|
|
|
|
|
class LightHuePlugin(LightPlugin):
|
|
"""
|
|
Philips Hue lights plugin.
|
|
|
|
Requires:
|
|
|
|
* **phue** (``pip install phue``)
|
|
|
|
Triggers:
|
|
|
|
- :class:`platypush.message.event.light.LightAnimationStartedEvent` when an animation is started.
|
|
- :class:`platypush.message.event.light.LightAnimationStoppedEvent` when an animation is stopped.
|
|
|
|
"""
|
|
|
|
MAX_BRI = 255
|
|
MAX_SAT = 255
|
|
MAX_HUE = 65535
|
|
ANIMATION_CTRL_QUEUE_NAME = 'platypush/light/hue/AnimationCtrl'
|
|
_BRIDGE_RECONNECT_SECONDS = 5
|
|
_MAX_RECONNECT_TRIES = 5
|
|
|
|
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):
|
|
"""
|
|
:param bridge: Bridge address or hostname
|
|
:type bridge: str
|
|
|
|
:param lights: Default lights to be controlled (default: all)
|
|
:type lights: list[str]
|
|
|
|
:param groups Default groups to be controlled (default: all)
|
|
:type groups: list[str]
|
|
"""
|
|
|
|
super().__init__()
|
|
|
|
self.bridge_address = bridge
|
|
self.bridge = None
|
|
self.logger.info('Initializing Hue lights plugin - bridge: "{}"'.format(self.bridge_address))
|
|
|
|
self.connect()
|
|
self.lights = []
|
|
self.groups = []
|
|
|
|
if lights:
|
|
self.lights = lights
|
|
elif groups:
|
|
self.groups = groups
|
|
self._expand_groups()
|
|
else:
|
|
# noinspection PyUnresolvedReferences
|
|
self.lights = [light.name for light in self.bridge.lights]
|
|
|
|
self.animation_thread = None
|
|
self.animations = {}
|
|
self._animation_stop = Event()
|
|
self._init_animations()
|
|
self.logger.info('Configured lights: "{}"'.format(self.lights))
|
|
|
|
def _expand_groups(self):
|
|
groups = [g for g in self.bridge.groups if g.name in self.groups]
|
|
for group in groups:
|
|
for light in group.lights:
|
|
self.lights += [light.name]
|
|
|
|
def _init_animations(self):
|
|
self.animations = {
|
|
'groups': {},
|
|
'lights': {},
|
|
}
|
|
|
|
for group in self.bridge.groups:
|
|
self.animations['groups'][group.group_id] = None
|
|
for light in self.bridge.lights:
|
|
self.animations['lights'][light.light_id] = None
|
|
|
|
@action
|
|
def connect(self):
|
|
"""
|
|
Connect to the configured Hue bridge. If the device hasn't been paired
|
|
yet, uncomment the ``.connect()`` and ``.get_api()`` lines and retry
|
|
after clicking the pairing button on your bridge.
|
|
"""
|
|
|
|
# Lazy init
|
|
if not self.bridge:
|
|
from phue import Bridge, PhueRegistrationException
|
|
success = False
|
|
n_tries = 0
|
|
|
|
while not success:
|
|
try:
|
|
n_tries += 1
|
|
self.bridge = Bridge(self.bridge_address)
|
|
success = True
|
|
except PhueRegistrationException as e:
|
|
self.logger.warning('Bridge registration error: {}'.
|
|
format(str(e)))
|
|
|
|
if n_tries >= self._MAX_RECONNECT_TRIES:
|
|
self.logger.error(('Bridge registration failed after ' +
|
|
'{} attempts').format(n_tries))
|
|
break
|
|
|
|
time.sleep(self._BRIDGE_RECONNECT_SECONDS)
|
|
|
|
self.logger.info('Bridge connected')
|
|
self.get_scenes()
|
|
else:
|
|
self.logger.info('Bridge already connected')
|
|
|
|
@action
|
|
def get_scenes(self):
|
|
"""
|
|
Get the available scenes on the devices.
|
|
|
|
:returns: The scenes configured on the bridge.
|
|
|
|
Example output::
|
|
|
|
{
|
|
"scene-id-1": {
|
|
"name": "Scene 1",
|
|
"lights": [
|
|
"1",
|
|
"3"
|
|
],
|
|
|
|
"owner": "owner-id",
|
|
"recycle": true,
|
|
"locked": false,
|
|
"appdata": {},
|
|
"picture": "",
|
|
"lastupdated": "2018-06-01T00:00:00",
|
|
"version": 1
|
|
}
|
|
}
|
|
|
|
"""
|
|
|
|
return {
|
|
id: {
|
|
'id': id,
|
|
**scene,
|
|
}
|
|
for id, scene in self.bridge.get_scene().items()
|
|
}
|
|
|
|
@action
|
|
def get_lights(self):
|
|
"""
|
|
Get the configured lights.
|
|
|
|
:returns: List of available lights as id->dict.
|
|
|
|
Example::
|
|
|
|
{
|
|
"1": {
|
|
"state": {
|
|
"on": true,
|
|
"bri": 254,
|
|
"hue": 1532,
|
|
"sat": 215,
|
|
"effect": "none",
|
|
"xy": [
|
|
0.6163,
|
|
0.3403
|
|
],
|
|
|
|
"ct": 153,
|
|
"alert": "none",
|
|
"colormode": "hs",
|
|
"reachable": true
|
|
},
|
|
|
|
"type": "Extended color light",
|
|
"name": "Lightbulb 1",
|
|
"modelid": "LCT001",
|
|
"manufacturername": "Philips",
|
|
"uniqueid": "00:11:22:33:44:55:66:77-88",
|
|
"swversion": "5.105.0.21169"
|
|
}
|
|
}
|
|
|
|
"""
|
|
|
|
return {
|
|
id: {
|
|
'id': id,
|
|
**light,
|
|
}
|
|
for id, light in self.bridge.get_light().items()
|
|
}
|
|
|
|
@action
|
|
def get_groups(self):
|
|
"""
|
|
Get the list of configured light groups.
|
|
|
|
:returns: List of configured light groups as id->dict.
|
|
|
|
Example::
|
|
|
|
{
|
|
"1": {
|
|
"name": "Living Room",
|
|
"lights": [
|
|
"16",
|
|
"13",
|
|
"12",
|
|
"11",
|
|
"10",
|
|
"9",
|
|
"1",
|
|
"3"
|
|
],
|
|
|
|
"type": "Room",
|
|
"state": {
|
|
"all_on": true,
|
|
"any_on": true
|
|
},
|
|
|
|
"class": "Living room",
|
|
"action": {
|
|
"on": true,
|
|
"bri": 241,
|
|
"hue": 37947,
|
|
"sat": 221,
|
|
"effect": "none",
|
|
"xy": [
|
|
0.2844,
|
|
0.2609
|
|
],
|
|
|
|
"ct": 153,
|
|
"alert": "none",
|
|
"colormode": "hs"
|
|
}
|
|
}
|
|
}
|
|
|
|
"""
|
|
|
|
return {
|
|
id: {
|
|
'id': id,
|
|
**group,
|
|
}
|
|
for id, group in self.bridge.get_group().items()
|
|
}
|
|
|
|
@action
|
|
def get_animations(self):
|
|
"""
|
|
Get the list of running light animations.
|
|
|
|
:returns: dict.
|
|
|
|
Structure::
|
|
|
|
{
|
|
"groups": {
|
|
"id_1": {
|
|
"type": "color_transition",
|
|
"hue_range": [0,65535],
|
|
"sat_range": [0,255],
|
|
"bri_range": [0,255],
|
|
"hue_step": 10,
|
|
"sat_step": 10,
|
|
"bri_step": 2,
|
|
"transition_seconds": 2
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"lights": {
|
|
"id_1": {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
return self.animations
|
|
|
|
def _exec(self, attr, *args, **kwargs):
|
|
try:
|
|
self.connect()
|
|
self.stop_animation()
|
|
except Exception as e:
|
|
# Reset bridge connection
|
|
self.bridge = None
|
|
raise e
|
|
|
|
lights = []
|
|
groups = []
|
|
|
|
if 'lights' in kwargs:
|
|
lights = kwargs.pop('lights').split(',').strip() \
|
|
if isinstance(lights, str) else kwargs.pop('lights')
|
|
if 'groups' in kwargs:
|
|
groups = kwargs.pop('groups').split(',').strip() \
|
|
if isinstance(groups, str) else kwargs.pop('groups')
|
|
|
|
if not lights and not groups:
|
|
lights = self.lights
|
|
groups = self.groups
|
|
|
|
if not self.bridge:
|
|
self.connect()
|
|
|
|
try:
|
|
if attr == 'scene':
|
|
self.bridge.run_scene(groups[0], kwargs.pop('name'))
|
|
else:
|
|
if groups:
|
|
self.bridge.set_group(groups, attr, *args, **kwargs)
|
|
if lights:
|
|
self.bridge.set_light(lights, attr, *args, **kwargs)
|
|
except Exception as e:
|
|
# Reset bridge connection
|
|
self.bridge = None
|
|
raise e
|
|
|
|
@action
|
|
def set_light(self, light, **kwargs):
|
|
"""
|
|
Set a light (or lights) property.
|
|
|
|
:param light: Light or lights to set. Can be a string representing the light name,
|
|
a light object, a list of string, or a list of light objects.
|
|
:param kwargs: key-value list of parameters to set.
|
|
|
|
Example call::
|
|
|
|
{
|
|
"type": "request",
|
|
"target": "hostname",
|
|
"action": "light.hue.set_light",
|
|
"args": {
|
|
"light": "Bulb 1",
|
|
"sat": 255
|
|
}
|
|
}
|
|
|
|
"""
|
|
|
|
self.connect()
|
|
self.bridge.set_light(light, **kwargs)
|
|
|
|
@action
|
|
def set_group(self, group, **kwargs):
|
|
"""
|
|
Set a group (or groups) property.
|
|
|
|
:param group: Group or groups to set. Can be a string representing the group name, a group object, a list of strings, or a list of group objects.
|
|
:param kwargs: key-value list of parameters to set.
|
|
|
|
Example call::
|
|
|
|
{
|
|
"type": "request",
|
|
"target": "hostname",
|
|
"action": "light.hue.set_group",
|
|
"args": {
|
|
"light": "Living Room",
|
|
"sat": 255
|
|
}
|
|
}
|
|
|
|
"""
|
|
|
|
self.connect()
|
|
self.bridge.set_group(group, **kwargs)
|
|
|
|
@action
|
|
def on(self, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Turn lights/groups on.
|
|
|
|
:param lights: Lights to turn on (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to turn on (names or group objects). Default: plugin default groups
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('on', True, lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def off(self, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Turn lights/groups off.
|
|
|
|
:param lights: Lights to turn off (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to turn off (names or group objects). Default: plugin default groups
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('on', False, lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def toggle(self, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Toggle lights/groups on/off.
|
|
|
|
:param lights: Lights to turn off (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to turn off (names or group objects). Default: plugin default groups
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
lights_on = []
|
|
lights_off = []
|
|
groups_on = []
|
|
groups_off = []
|
|
|
|
if groups:
|
|
all_groups = self.bridge.get_group().values()
|
|
|
|
groups_on = [
|
|
group['name'] for group in all_groups
|
|
if group['name'] in groups and group['state']['any_on'] is True
|
|
]
|
|
|
|
groups_off = [
|
|
group['name'] for group in all_groups
|
|
if group['name'] in groups and group['state']['any_on'] is False
|
|
]
|
|
|
|
if not groups and not lights:
|
|
lights = self.lights
|
|
|
|
if lights:
|
|
all_lights = self.bridge.get_light().values()
|
|
|
|
lights_on = [
|
|
light['name'] for light in all_lights
|
|
if light['name'] in lights and light['state']['on'] is True
|
|
]
|
|
|
|
lights_off = [
|
|
light['name'] for light in all_lights
|
|
if light['name'] in lights and light['state']['on'] is False
|
|
]
|
|
|
|
if lights_on or groups_on:
|
|
self._exec('on', False, lights=lights_on, groups=groups_on, **kwargs)
|
|
|
|
if lights_off or groups_off:
|
|
self._exec('on', True, lights=lights_off, groups=groups_off, **kwargs)
|
|
|
|
@action
|
|
def bri(self, value, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Set lights/groups brightness.
|
|
|
|
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
|
:param value: Brightness value (range: 0-255)
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('bri', int(value) % (self.MAX_BRI + 1),
|
|
lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def sat(self, value, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Set lights/groups saturation.
|
|
|
|
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
|
:param value: Saturation value (range: 0-255)
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('sat', int(value) % (self.MAX_SAT + 1),
|
|
lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def hue(self, value, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Set lights/groups color hue.
|
|
|
|
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
|
:param value: Hue value (range: 0-65535)
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('hue', int(value) % (self.MAX_HUE + 1),
|
|
lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def xy(self, value, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Set lights/groups XY colors.
|
|
|
|
:param value: xY value
|
|
:type value: list[float] containing the two values
|
|
:param lights: List of lights.
|
|
:param groups: List of groups.
|
|
"""
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('xy', value, lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def ct(self, value, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Set lights/groups color temperature.
|
|
|
|
:param value: Temperature value (range: 0-255)
|
|
:type value: int
|
|
:param lights: List of lights.
|
|
:param groups: List of groups.
|
|
"""
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('ct', value, lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def delta_bri(self, delta, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Change lights/groups brightness by a delta [-100, 100] compared to the current state.
|
|
|
|
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
|
:param delta: Brightness delta value (range: -100, 100)
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
|
|
if lights:
|
|
bri = statistics.mean([
|
|
light['state']['bri']
|
|
for light in self.bridge.get_light().values()
|
|
if light['name'] in lights
|
|
])
|
|
elif groups:
|
|
bri = statistics.mean([
|
|
group['action']['bri']
|
|
for group in self.bridge.get_group().values()
|
|
if group['name'] in groups
|
|
])
|
|
else:
|
|
bri = statistics.mean([
|
|
light['state']['bri']
|
|
for light in self.bridge.get_light().values()
|
|
if light['name'] in self.lights
|
|
])
|
|
|
|
delta *= (self.MAX_BRI / 100)
|
|
if bri + delta < 0:
|
|
bri = 0
|
|
elif bri + delta > self.MAX_BRI:
|
|
bri = self.MAX_BRI
|
|
else:
|
|
bri += delta
|
|
|
|
return self._exec('bri', int(bri), lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def delta_sat(self, delta, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Change lights/groups saturation by a delta [-100, 100] compared to the current state.
|
|
|
|
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
|
:param delta: Saturation delta value (range: -100, 100)
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
|
|
if lights:
|
|
sat = statistics.mean([
|
|
light['state']['sat']
|
|
for light in self.bridge.get_light().values()
|
|
if light['name'] in lights
|
|
])
|
|
elif groups:
|
|
sat = statistics.mean([
|
|
group['action']['sat']
|
|
for group in self.bridge.get_group().values()
|
|
if group['name'] in groups
|
|
])
|
|
else:
|
|
sat = statistics.mean([
|
|
light['state']['sat']
|
|
for light in self.bridge.get_light().values()
|
|
if light['name'] in self.lights
|
|
])
|
|
|
|
delta *= (self.MAX_SAT / 100)
|
|
if sat + delta < 0:
|
|
sat = 0
|
|
elif sat + delta > self.MAX_SAT:
|
|
sat = self.MAX_SAT
|
|
else:
|
|
sat += delta
|
|
|
|
return self._exec('sat', int(sat), lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def delta_hue(self, delta, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Change lights/groups hue by a delta [-100, 100] compared to the current state.
|
|
|
|
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
|
:param delta: Hue delta value (range: -100, 100)
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
|
|
if lights:
|
|
hue = statistics.mean([
|
|
light['state']['hue']
|
|
for light in self.bridge.get_light().values()
|
|
if light['name'] in lights
|
|
])
|
|
elif groups:
|
|
hue = statistics.mean([
|
|
group['action']['hue']
|
|
for group in self.bridge.get_group().values()
|
|
if group['name'] in groups
|
|
])
|
|
else:
|
|
hue = statistics.mean([
|
|
light['state']['hue']
|
|
for light in self.bridge.get_light().values()
|
|
if light['name'] in self.lights
|
|
])
|
|
|
|
delta *= (self.MAX_HUE / 100)
|
|
if hue + delta < 0:
|
|
hue = 0
|
|
elif hue + delta > self.MAX_HUE:
|
|
hue = self.MAX_HUE
|
|
else:
|
|
hue += delta
|
|
|
|
return self._exec('hue', int(hue), lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def scene(self, name, lights=None, groups=None, **kwargs):
|
|
"""
|
|
Set a scene by name.
|
|
|
|
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
|
:param name: Name of the scene
|
|
"""
|
|
|
|
if groups is None:
|
|
groups = []
|
|
if lights is None:
|
|
lights = []
|
|
return self._exec('scene', name=name, lights=lights, groups=groups, **kwargs)
|
|
|
|
@action
|
|
def is_animation_running(self):
|
|
"""
|
|
:returns: True if there is an animation running, false otherwise.
|
|
"""
|
|
|
|
return self.animation_thread is not None and self.animation_thread.is_alive()
|
|
|
|
@action
|
|
def stop_animation(self):
|
|
"""
|
|
Stop a running animation.
|
|
"""
|
|
if self.is_animation_running():
|
|
self._animation_stop.set()
|
|
self._init_animations()
|
|
|
|
@action
|
|
def animate(self, animation, duration=None,
|
|
hue_range=None, sat_range=None,
|
|
bri_range=None, lights=None, groups=None,
|
|
hue_step=1000, sat_step=2, bri_step=1, transition_seconds=1.0):
|
|
"""
|
|
Run a lights animation.
|
|
|
|
:param animation: Animation name. Supported types: **color_transition** and **blink**
|
|
:type animation: str
|
|
|
|
:param duration: Animation duration in seconds (default: None, i.e. continue until stop)
|
|
:type duration: float
|
|
|
|
:param hue_range: If you selected a ``color_transition``, this will specify the hue range of your color ``color_transition``.
|
|
Default: [0, 65535]
|
|
:type hue_range: list[int]
|
|
|
|
:param sat_range: If you selected a color ``color_transition``, this will specify the saturation range of your color
|
|
``color_transition``. Default: [0, 255]
|
|
:type sat_range: list[int]
|
|
|
|
:param bri_range: If you selected a color ``color_transition``, this will specify the brightness range of your color
|
|
``color_transition``. Default: [254, 255] :type bri_range: list[int]
|
|
|
|
:param lights: Lights to control (names, IDs or light objects). Default: plugin default lights
|
|
:param groups: Groups to control (names, IDs or group objects). Default: plugin default groups
|
|
|
|
:param hue_step: If you selected a color ``color_transition``, this will specify by how much the color hue will change
|
|
between iterations. Default: 1000 :type hue_step: int
|
|
|
|
:param sat_step: If you selected a color ``color_transition``, this will specify by how much the saturation will change
|
|
between iterations. Default: 2 :type sat_step: int
|
|
|
|
:param bri_step: If you selected a color ``color_transition``, this will specify by how much the brightness will change
|
|
between iterations. Default: 1 :type bri_step: int
|
|
|
|
:param transition_seconds: Time between two transitions or blinks in seconds. Default: 1.0
|
|
:type transition_seconds: float
|
|
"""
|
|
|
|
self.stop_animation()
|
|
self._animation_stop.clear()
|
|
|
|
if bri_range is None:
|
|
bri_range = [self.MAX_BRI - 1, self.MAX_BRI]
|
|
if sat_range is None:
|
|
sat_range = [0, self.MAX_SAT]
|
|
if hue_range is None:
|
|
hue_range = [0, self.MAX_HUE]
|
|
if groups:
|
|
groups = [g for g in self.bridge.groups if g.name in groups or g.group_id in groups]
|
|
lights = lights or []
|
|
for group in groups:
|
|
lights.extend([light.name for light in group.lights])
|
|
elif lights:
|
|
lights = [light.name for light in self.bridge.lights if light.name in lights or light.light_id in lights]
|
|
else:
|
|
lights = self.lights
|
|
|
|
info = {
|
|
'type': animation,
|
|
'duration': duration,
|
|
'hue_range': hue_range,
|
|
'sat_range': sat_range,
|
|
'bri_range': bri_range,
|
|
'hue_step': hue_step,
|
|
'sat_step': sat_step,
|
|
'bri_step': bri_step,
|
|
'transition_seconds': transition_seconds,
|
|
}
|
|
|
|
if groups:
|
|
for group in groups:
|
|
self.animations['groups'][group.group_id] = info
|
|
|
|
for light in self.bridge.lights:
|
|
if light.name in lights:
|
|
self.animations['lights'][light.light_id] = info
|
|
|
|
def _initialize_light_attrs(lights):
|
|
if animation == self.Animation.COLOR_TRANSITION:
|
|
return {light: {
|
|
'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 light in lights}
|
|
elif animation == self.Animation.BLINK:
|
|
return {light: {
|
|
'on': True,
|
|
'bri': self.MAX_BRI,
|
|
'transitiontime': 0,
|
|
} for light 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
|
|
else:
|
|
continue
|
|
|
|
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():
|
|
return self._animation_stop.is_set()
|
|
|
|
def _animate_thread(lights):
|
|
set_thread_name('HueAnimate')
|
|
get_bus().post(LightAnimationStartedEvent(lights=lights, groups=groups, animation=animation))
|
|
|
|
lights = _initialize_light_attrs(lights)
|
|
animation_start_time = time.time()
|
|
stop_animation = False
|
|
|
|
while not stop_animation and not (duration and time.time() - animation_start_time > duration):
|
|
try:
|
|
if animation == self.Animation.COLOR_TRANSITION:
|
|
for (light, attrs) in lights.items():
|
|
self.logger.debug('Setting {} to {}'.format(light, attrs))
|
|
self.bridge.set_light(light, attrs)
|
|
elif animation == self.Animation.BLINK:
|
|
conf = lights[list(lights.keys())[0]]
|
|
self.logger.debug('Setting lights to {}'.format(conf))
|
|
|
|
if groups:
|
|
self.bridge.set_group([g.name for g in groups], conf)
|
|
else:
|
|
self.bridge.set_light(lights.keys(), conf)
|
|
|
|
if transition_seconds:
|
|
time.sleep(transition_seconds)
|
|
|
|
stop_animation = _should_stop()
|
|
except Exception as e:
|
|
self.logger.warning(e)
|
|
time.sleep(2)
|
|
|
|
lights = _next_light_attrs(lights)
|
|
|
|
get_bus().post(LightAnimationStoppedEvent(lights=lights, groups=groups, animation=animation))
|
|
self.animation_thread = None
|
|
|
|
self.animation_thread = Thread(target=_animate_thread,
|
|
name='HueAnimate',
|
|
args=(lights,))
|
|
self.animation_thread.start()
|
|
|
|
@property
|
|
def switches(self) -> List[dict]:
|
|
"""
|
|
:returns: Implements :meth:`platypush.plugins.switch.SwitchPlugin.switches` and returns the status of the
|
|
configured lights. Example:
|
|
|
|
.. code-block:: json
|
|
|
|
[
|
|
{
|
|
"id": "3",
|
|
"name": "Lightbulb 1",
|
|
"on": true,
|
|
"bri": 254,
|
|
"hue": 1532,
|
|
"sat": 215,
|
|
"effect": "none",
|
|
"xy": [
|
|
0.6163,
|
|
0.3403
|
|
],
|
|
"ct": 153,
|
|
"alert": "none",
|
|
"colormode": "hs",
|
|
"reachable": true
|
|
"type": "Extended color light",
|
|
"modelid": "LCT001",
|
|
"manufacturername": "Philips",
|
|
"uniqueid": "00:11:22:33:44:55:66:77-88",
|
|
"swversion": "5.105.0.21169"
|
|
}
|
|
]
|
|
|
|
"""
|
|
|
|
return [
|
|
{
|
|
'id': id,
|
|
**light.pop('state', {}),
|
|
**light,
|
|
}
|
|
for id, light in self.bridge.get_light().items()
|
|
]
|
|
|
|
|
|
# vim:sw=4:ts=4:et:
|