platypush/platypush/plugins/light/hue/__init__.py

1180 lines
37 KiB
Python

import random
import statistics
import time
from enum import Enum
from threading import Thread, Event
from typing import Iterable, Union, Mapping, Any, Set
from platypush.context import get_bus
from platypush.entities import Entity
from platypush.entities.lights import Light as LightEntity
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
MIN_CT = 154
MAX_CT = 500
ANIMATION_CTRL_QUEUE_NAME = 'platypush/light/hue/AnimationCtrl'
_BRIDGE_RECONNECT_SECONDS = 5
_MAX_RECONNECT_TRIES = 5
_UNINITIALIZED_BRIDGE_ERR = 'The Hue bridge is not initialized'
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 = set()
self.groups = set()
self._cached_lights = {}
if lights:
self.lights = set(lights)
elif groups:
self.groups = set(groups)
self.lights.update(self._expand_groups(self.groups))
else:
self.lights = {light['name'] for light in self._get_lights().values()}
self.animation_thread = None
self.animations = {}
self._animation_stop = Event()
self._init_animations()
self.logger.info(f'Configured lights: {self.lights}')
def _expand_groups(self, groups: Iterable[str]) -> Set[str]:
lights = set()
light_id_to_name = {
light_id: light['name'] for light_id, light in self._get_lights().items()
}
groups_ = [g for g in self._get_groups().values() if g.get('name') in groups]
for group in groups_:
for light_id in group.get('lights', []):
light_name = light_id_to_name.get(light_id)
if light_name:
lights.add(light_name)
return lights
def _init_animations(self):
self.animations = {
'groups': {},
'lights': {},
}
for group_id in self._get_groups():
self.animations['groups'][group_id] = None
for light_id in self._get_lights():
self.animations['lights'][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._get_scenes().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._get_lights().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._get_groups().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
assert self.bridge, self._UNINITIALIZED_BRIDGE_ERR
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':
assert groups, 'No groups specified'
self.bridge.run_scene(list(groups)[0], kwargs.pop('name'))
else:
if groups:
self.bridge.set_group(list(groups), attr, *args, **kwargs)
if lights:
self.bridge.set_light(list(lights), attr, *args, **kwargs)
except Exception as e:
# Reset bridge connection
self.bridge = None
raise e
return self._get_lights()
@action
def set_lights(self, lights, **kwargs):
"""
Set a set of properties on a set of lights.
:param light: List of lights to set. Each item can represent a light
name or ID.
:param kwargs: key-value list of the parameters to set.
Example call::
{
"type": "request",
"action": "light.hue.set_light",
"args": {
"lights": ["Bulb 1", "Bulb 2"],
"sat": 255
}
}
"""
self.connect()
assert self.bridge, self._UNINITIALIZED_BRIDGE_ERR
all_lights = self._get_lights()
for i, l in enumerate(lights):
if str(l) in all_lights:
lights[i] = all_lights[str(l)]['name']
# Convert entity attributes to local attributes
if kwargs.get('saturation') is not None:
kwargs['sat'] = kwargs.pop('saturation')
if kwargs.get('brightness') is not None:
kwargs['bri'] = kwargs.pop('brightness')
if kwargs.get('temperature') is not None:
kwargs['ct'] = kwargs.pop('temperature')
# "Unroll" the map
args = []
for arg, value in kwargs.items():
args += [arg, value]
self.bridge.set_light(lights, *args)
return self._get_lights()
@action
def set_group(self, group, **kwargs):
"""
Set a group (or groups) property.
:param group: Group or groups to set. It 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",
"action": "light.hue.set_group",
"args": {
"light": "Living Room",
"sat": 255
}
}
"""
self.connect()
assert self.bridge, self._UNINITIALIZED_BRIDGE_ERR
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._get_groups().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._get_lights()
lights_on = [
light['name']
for light_id, light in all_lights.items()
if (light_id in lights or light['name'] in lights)
and light['state']['on'] is True
]
lights_off = [
light['name']
for light_id, light in all_lights.items()
if (light_id in lights or 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: 154-500)
: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._get_lights().values()
if light['name'] in lights
]
)
elif groups:
bri = statistics.mean(
[
group['action']['bri']
for group in self._get_groups().values()
if group['name'] in groups
]
)
else:
bri = statistics.mean(
[
light['state']['bri']
for light in self._get_lights().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._get_lights().values()
if light['name'] in lights
]
)
elif groups:
sat = statistics.mean(
[
group['action']['sat']
for group in self._get_groups().values()
if group['name'] in groups
]
)
else:
sat = statistics.mean(
[
light['state']['sat']
for light in self._get_lights().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._get_lights().values()
if light['name'] in lights
]
)
elif groups:
hue = statistics.mean(
[
group['action']['hue']
for group in self._get_groups().values()
if group['name'] in groups
]
)
else:
hue = statistics.mean(
[
light['state']['hue']
for light in self._get_lights().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()
all_lights = self._get_lights()
bri_range = bri_range or [self.MAX_BRI - 1, self.MAX_BRI]
sat_range = sat_range or [0, self.MAX_SAT]
hue_range = hue_range or [0, self.MAX_HUE]
if groups:
groups = {
group_id: group
for group_id, group in self._get_groups().items()
if group.get('name') in groups or group_id in groups
}
lights = set(lights or [])
lights.update(self._expand_groups([g['name'] for g in groups.values()]))
elif lights:
lights = {
light['name']
for light_id, light in all_lights.items()
if light['name'] in lights or int(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_id in groups:
self.animations['groups'][group_id] = info
for light_id, light in all_lights.items():
if light['name'] in lights:
self.animations['lights'][light_id] = info
def _initialize_light_attrs(lights):
lights_by_name = {
light['name']: light for light in self._get_lights().values()
}
if animation == self.Animation.COLOR_TRANSITION:
return {
light: {
**(
{'hue': random.randint(hue_range[0], hue_range[1])} # type: ignore
if 'hue' in lights_by_name.get(light, {}).get('state', {})
else {}
),
**(
{'sat': random.randint(sat_range[0], sat_range[1])} # type: ignore
if 'sat' in lights_by_name.get(light, {}).get('state', {})
else {}
),
**(
{'bri': random.randint(bri_range[0], bri_range[1])} # type: ignore
if 'bri' in lights_by_name.get(light, {}).get('state', {})
else {}
),
}
for light in lights
}
elif animation == self.Animation.BLINK:
return {
light: {
'on': True,
**({'bri': self.MAX_BRI} if 'bri' in light else {}),
'transitiontime': 0,
}
for light in lights
}
raise AssertionError(f'Unknown animation type: {animation}')
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': not attrs['on'],
'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=list((groups or {}).keys()),
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
):
assert self.bridge, self._UNINITIALIZED_BRIDGE_ERR
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.values()], 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=list(lights.keys()),
groups=list((groups or {}).keys()),
animation=animation,
)
)
self.animation_thread = None
self.animation_thread = Thread(
target=_animate_thread, name='HueAnimate', args=(lights,)
)
self.animation_thread.start()
def _get_light_attr(self, light, attr: str):
try:
return getattr(light, attr, None)
except KeyError:
return None
def transform_entities(
self, entities: Union[Iterable[Union[dict, Entity]], Mapping[Any, dict]]
) -> Iterable[Entity]:
new_entities = []
if isinstance(entities, dict):
entities = [{'id': id, **e} for id, e in entities.items()]
for entity in entities:
if isinstance(entity, Entity):
new_entities.append(entity)
elif isinstance(entity, dict):
new_entities.append(
LightEntity(
id=entity['id'],
name=entity['name'],
description=entity.get('type'),
on=entity.get('state', {}).get('on', False),
brightness=entity.get('state', {}).get('bri'),
saturation=entity.get('state', {}).get('sat'),
hue=entity.get('state', {}).get('hue'),
temperature=entity.get('state', {}).get('ct'),
colormode=entity.get('colormode'),
reachable=entity.get('state', {}).get('reachable'),
x=entity['state']['xy'][0]
if entity.get('state', {}).get('xy')
else None,
y=entity['state']['xy'][1]
if entity.get('state', {}).get('xy')
else None,
effect=entity.get('state', {}).get('effect'),
**(
{
'hue_min': 0,
'hue_max': self.MAX_HUE,
}
if entity.get('state', {}).get('hue') is not None
else {
'hue_min': None,
'hue_max': None,
}
),
**(
{
'saturation_min': 0,
'saturation_max': self.MAX_SAT,
}
if entity.get('state', {}).get('sat') is not None
else {
'saturation_min': None,
'saturation_max': None,
}
),
**(
{
'brightness_min': 0,
'brightness_max': self.MAX_BRI,
}
if entity.get('state', {}).get('bri') is not None
else {
'brightness_min': None,
'brightness_max': None,
}
),
**(
{
'temperature_min': self.MIN_CT,
'temperature_max': self.MAX_CT,
}
if entity.get('state', {}).get('ct') is not None
else {
'temperature_min': None,
'temperature_max': None,
}
),
)
)
return super().transform_entities(new_entities) # type: ignore
def _get_lights(self) -> dict:
assert self.bridge, self._UNINITIALIZED_BRIDGE_ERR
lights = self.bridge.get_light()
lights = {id: light for id, light in lights.items() if not light.get('recycle')}
self._cached_lights = lights
self.publish_entities(lights) # type: ignore
return lights
def _get_groups(self) -> dict:
assert self.bridge, self._UNINITIALIZED_BRIDGE_ERR
groups = self.bridge.get_group() or {}
return {id: group for id, group in groups.items() if not group.get('recycle')}
def _get_scenes(self) -> dict:
assert self.bridge, self._UNINITIALIZED_BRIDGE_ERR
scenes = self.bridge.get_scene() or {}
return {id: scene for id, scene in scenes.items() if not scene.get('recycle')}
@action
def status(self) -> Iterable[LightEntity]:
lights = self.transform_entities(self._get_lights())
for light in lights:
light.id = light.external_id
for attr, value in (light.data or {}).items():
setattr(light, attr, value)
del light.external_id
del light.data
return lights
# vim:sw=4:ts=4:et: