mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
LIFX: Move light effects to external library (#8222)
* LIFX: Move light effects to external library This moves the LIFX light effects to the external library aiolifx_effects. To get the light state synchronized between that library and HA, the LIFX platform no longer maintains the light state itself. Instead, it uses the cached state that aiolifx maintains. The reorganization also includes the addition of a cleanup handler. * Fix style
This commit is contained in:
parent
442dcd584b
commit
af54311718
@ -280,7 +280,7 @@ omit =
|
||||
homeassistant/components/light/flux_led.py
|
||||
homeassistant/components/light/hue.py
|
||||
homeassistant/components/light/hyperion.py
|
||||
homeassistant/components/light/lifx/*.py
|
||||
homeassistant/components/light/lifx.py
|
||||
homeassistant/components/light/lifx_legacy.py
|
||||
homeassistant/components/light/limitlessled.py
|
||||
homeassistant/components/light/mystrom.py
|
||||
|
@ -11,18 +11,19 @@ import math
|
||||
from os import path
|
||||
from functools import partial
|
||||
from datetime import timedelta
|
||||
import async_timeout
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (
|
||||
Light, DOMAIN, PLATFORM_SCHEMA, LIGHT_TURN_ON_SCHEMA,
|
||||
ATTR_BRIGHTNESS, ATTR_RGB_COLOR,
|
||||
ATTR_XY_COLOR, ATTR_COLOR_TEMP, ATTR_TRANSITION, ATTR_EFFECT,
|
||||
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, ATTR_RGB_COLOR,
|
||||
ATTR_XY_COLOR, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_TRANSITION, ATTR_EFFECT,
|
||||
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR,
|
||||
SUPPORT_XY_COLOR, SUPPORT_TRANSITION, SUPPORT_EFFECT,
|
||||
VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT,
|
||||
preprocess_turn_on_alternatives)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant import util
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
@ -30,34 +31,79 @@ from homeassistant.helpers.service import extract_entity_ids
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
from . import effects as lifx_effects
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['aiolifx==0.4.8']
|
||||
REQUIREMENTS = ['aiolifx==0.5.0', 'aiolifx_effects==0.1.0']
|
||||
|
||||
UDP_BROADCAST_PORT = 56700
|
||||
|
||||
# Delay (in ms) expected for changes to take effect in the physical bulb
|
||||
BULB_LATENCY = 500
|
||||
|
||||
CONF_SERVER = 'server'
|
||||
|
||||
SERVICE_LIFX_SET_STATE = 'lifx_set_state'
|
||||
|
||||
ATTR_HSBK = 'hsbk'
|
||||
ATTR_INFRARED = 'infrared'
|
||||
ATTR_POWER = 'power'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_SERVER, default='0.0.0.0'): cv.string,
|
||||
})
|
||||
|
||||
SERVICE_LIFX_SET_STATE = 'lifx_set_state'
|
||||
|
||||
ATTR_INFRARED = 'infrared'
|
||||
ATTR_POWER = 'power'
|
||||
|
||||
LIFX_SET_STATE_SCHEMA = LIGHT_TURN_ON_SCHEMA.extend({
|
||||
ATTR_INFRARED: vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)),
|
||||
ATTR_POWER: cv.boolean,
|
||||
})
|
||||
|
||||
SERVICE_EFFECT_PULSE = 'lifx_effect_pulse'
|
||||
SERVICE_EFFECT_COLORLOOP = 'lifx_effect_colorloop'
|
||||
SERVICE_EFFECT_STOP = 'lifx_effect_stop'
|
||||
|
||||
ATTR_POWER_ON = 'power_on'
|
||||
ATTR_MODE = 'mode'
|
||||
ATTR_PERIOD = 'period'
|
||||
ATTR_CYCLES = 'cycles'
|
||||
ATTR_SPREAD = 'spread'
|
||||
ATTR_CHANGE = 'change'
|
||||
|
||||
PULSE_MODE_BLINK = 'blink'
|
||||
PULSE_MODE_BREATHE = 'breathe'
|
||||
PULSE_MODE_PING = 'ping'
|
||||
PULSE_MODE_STROBE = 'strobe'
|
||||
PULSE_MODE_SOLID = 'solid'
|
||||
|
||||
PULSE_MODES = [PULSE_MODE_BLINK, PULSE_MODE_BREATHE, PULSE_MODE_PING,
|
||||
PULSE_MODE_STROBE, PULSE_MODE_SOLID]
|
||||
|
||||
LIFX_EFFECT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(ATTR_POWER_ON, default=True): cv.boolean,
|
||||
})
|
||||
|
||||
LIFX_EFFECT_PULSE_SCHEMA = LIFX_EFFECT_SCHEMA.extend({
|
||||
ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT,
|
||||
ATTR_COLOR_NAME: cv.string,
|
||||
ATTR_RGB_COLOR: vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)),
|
||||
vol.Coerce(tuple)),
|
||||
ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
ATTR_KELVIN: vol.All(vol.Coerce(int), vol.Range(min=0)),
|
||||
ATTR_PERIOD: vol.All(vol.Coerce(float), vol.Range(min=0.05)),
|
||||
ATTR_CYCLES: vol.All(vol.Coerce(float), vol.Range(min=1)),
|
||||
ATTR_MODE: vol.In(PULSE_MODES),
|
||||
})
|
||||
|
||||
LIFX_EFFECT_COLORLOOP_SCHEMA = LIFX_EFFECT_SCHEMA.extend({
|
||||
ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT,
|
||||
ATTR_PERIOD: vol.All(vol.Coerce(float), vol.Clamp(min=0.05)),
|
||||
ATTR_CHANGE: vol.All(vol.Coerce(float), vol.Clamp(min=0, max=360)),
|
||||
ATTR_SPREAD: vol.All(vol.Coerce(float), vol.Clamp(min=0, max=360)),
|
||||
ATTR_TRANSITION: vol.All(vol.Coerce(float), vol.Range(min=0)),
|
||||
})
|
||||
|
||||
LIFX_EFFECT_STOP_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
@ -71,27 +117,79 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
server_addr = config.get(CONF_SERVER)
|
||||
|
||||
lifx_manager = LIFXManager(hass, async_add_devices)
|
||||
lifx_discovery = aiolifx.LifxDiscovery(hass.loop, lifx_manager)
|
||||
|
||||
coro = hass.loop.create_datagram_endpoint(
|
||||
partial(aiolifx.LifxDiscovery, hass.loop, lifx_manager),
|
||||
local_addr=(server_addr, UDP_BROADCAST_PORT))
|
||||
lambda: lifx_discovery, local_addr=(server_addr, UDP_BROADCAST_PORT))
|
||||
|
||||
hass.async_add_job(coro)
|
||||
|
||||
lifx_effects.setup(hass, lifx_manager)
|
||||
@callback
|
||||
def cleanup(event):
|
||||
"""Clean up resources."""
|
||||
lifx_discovery.cleanup()
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def find_hsbk(**kwargs):
|
||||
"""Find the desired color from a number of possible inputs."""
|
||||
hue, saturation, brightness, kelvin = [None]*4
|
||||
|
||||
preprocess_turn_on_alternatives(kwargs)
|
||||
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
hue, saturation, brightness = \
|
||||
color_util.color_RGB_to_hsv(*kwargs[ATTR_RGB_COLOR])
|
||||
saturation = convert_8_to_16(saturation)
|
||||
brightness = convert_8_to_16(brightness)
|
||||
kelvin = 3500
|
||||
|
||||
if ATTR_XY_COLOR in kwargs:
|
||||
hue, saturation = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR])
|
||||
saturation = convert_8_to_16(saturation)
|
||||
kelvin = 3500
|
||||
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
kelvin = int(color_util.color_temperature_mired_to_kelvin(
|
||||
kwargs[ATTR_COLOR_TEMP]))
|
||||
saturation = 0
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = convert_8_to_16(kwargs[ATTR_BRIGHTNESS])
|
||||
|
||||
hsbk = [hue, saturation, brightness, kelvin]
|
||||
return None if hsbk == [None]*4 else hsbk
|
||||
|
||||
|
||||
def merge_hsbk(base, change):
|
||||
"""Copy change on top of base, except when None."""
|
||||
if change is None:
|
||||
return None
|
||||
return list(map(lambda x, y: y if y is not None else x, base, change))
|
||||
|
||||
|
||||
class LIFXManager(object):
|
||||
"""Representation of all known LIFX entities."""
|
||||
|
||||
def __init__(self, hass, async_add_devices):
|
||||
"""Initialize the light."""
|
||||
import aiolifx_effects
|
||||
self.entities = {}
|
||||
self.hass = hass
|
||||
self.async_add_devices = async_add_devices
|
||||
self.effects_conductor = aiolifx_effects.Conductor(loop=hass.loop)
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
path.join(path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
self.register_set_state(descriptions)
|
||||
self.register_effects(descriptions)
|
||||
|
||||
def register_set_state(self, descriptions):
|
||||
"""Register the LIFX set_state service call."""
|
||||
@asyncio.coroutine
|
||||
def async_service_handle(service):
|
||||
"""Apply a service."""
|
||||
@ -99,22 +197,73 @@ class LIFXManager(object):
|
||||
for light in self.service_to_entities(service):
|
||||
if service.service == SERVICE_LIFX_SET_STATE:
|
||||
task = light.async_set_state(**service.data)
|
||||
tasks.append(hass.async_add_job(task))
|
||||
tasks.append(self.hass.async_add_job(task))
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
yield from asyncio.wait(tasks, loop=self.hass.loop)
|
||||
|
||||
descriptions = self.get_descriptions()
|
||||
|
||||
hass.services.async_register(
|
||||
self.hass.services.async_register(
|
||||
DOMAIN, SERVICE_LIFX_SET_STATE, async_service_handle,
|
||||
descriptions.get(SERVICE_LIFX_SET_STATE),
|
||||
schema=LIFX_SET_STATE_SCHEMA)
|
||||
|
||||
@staticmethod
|
||||
def get_descriptions():
|
||||
"""Load and return descriptions for our own service calls."""
|
||||
return load_yaml_config_file(
|
||||
path.join(path.dirname(__file__), 'services.yaml'))
|
||||
def register_effects(self, descriptions):
|
||||
"""Register the LIFX effects as hass service calls."""
|
||||
@asyncio.coroutine
|
||||
def async_service_handle(service):
|
||||
"""Apply a service, i.e. start an effect."""
|
||||
entities = self.service_to_entities(service)
|
||||
if entities:
|
||||
yield from self.start_effect(
|
||||
entities, service.service, **service.data)
|
||||
|
||||
self.hass.services.async_register(
|
||||
DOMAIN, SERVICE_EFFECT_PULSE, async_service_handle,
|
||||
descriptions.get(SERVICE_EFFECT_PULSE),
|
||||
schema=LIFX_EFFECT_PULSE_SCHEMA)
|
||||
|
||||
self.hass.services.async_register(
|
||||
DOMAIN, SERVICE_EFFECT_COLORLOOP, async_service_handle,
|
||||
descriptions.get(SERVICE_EFFECT_COLORLOOP),
|
||||
schema=LIFX_EFFECT_COLORLOOP_SCHEMA)
|
||||
|
||||
self.hass.services.async_register(
|
||||
DOMAIN, SERVICE_EFFECT_STOP, async_service_handle,
|
||||
descriptions.get(SERVICE_EFFECT_STOP),
|
||||
schema=LIFX_EFFECT_STOP_SCHEMA)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_effect(self, entities, service, **kwargs):
|
||||
"""Start a light effect on entities."""
|
||||
import aiolifx_effects
|
||||
devices = list(map(lambda l: l.device, entities))
|
||||
|
||||
if service == SERVICE_EFFECT_PULSE:
|
||||
effect = aiolifx_effects.EffectPulse(
|
||||
power_on=kwargs.get(ATTR_POWER_ON, None),
|
||||
period=kwargs.get(ATTR_PERIOD, None),
|
||||
cycles=kwargs.get(ATTR_CYCLES, None),
|
||||
mode=kwargs.get(ATTR_MODE, None),
|
||||
hsbk=find_hsbk(**kwargs),
|
||||
)
|
||||
yield from self.effects_conductor.start(effect, devices)
|
||||
elif service == SERVICE_EFFECT_COLORLOOP:
|
||||
preprocess_turn_on_alternatives(kwargs)
|
||||
|
||||
brightness = None
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = convert_8_to_16(kwargs[ATTR_BRIGHTNESS])
|
||||
|
||||
effect = aiolifx_effects.EffectColorloop(
|
||||
power_on=kwargs.get(ATTR_POWER_ON, None),
|
||||
period=kwargs.get(ATTR_PERIOD, None),
|
||||
change=kwargs.get(ATTR_CHANGE, None),
|
||||
spread=kwargs.get(ATTR_SPREAD, None),
|
||||
transition=kwargs.get(ATTR_TRANSITION, None),
|
||||
brightness=brightness,
|
||||
)
|
||||
yield from self.effects_conductor.start(effect, devices)
|
||||
elif service == SERVICE_EFFECT_STOP:
|
||||
yield from self.effects_conductor.stop(devices)
|
||||
|
||||
def service_to_entities(self, service):
|
||||
"""Return the known devices that a service call mentions."""
|
||||
@ -148,7 +297,7 @@ class LIFXManager(object):
|
||||
@callback
|
||||
def ready(self, device, msg):
|
||||
"""Handle the device once all data is retrieved."""
|
||||
entity = LIFXLight(device)
|
||||
entity = LIFXLight(device, self.effects_conductor)
|
||||
_LOGGER.debug("%s register READY", entity.who)
|
||||
self.entities[device.mac_addr] = entity
|
||||
self.async_add_devices([entity])
|
||||
@ -182,16 +331,13 @@ class AwaitAioLIFX:
|
||||
|
||||
@asyncio.coroutine
|
||||
def wait(self, method):
|
||||
"""Call an aiolifx method and wait for its response or a timeout."""
|
||||
"""Call an aiolifx method and wait for its response."""
|
||||
self.device = None
|
||||
self.message = None
|
||||
self.event.clear()
|
||||
method(self.callback)
|
||||
|
||||
while self.light.available and not self.event.is_set():
|
||||
try:
|
||||
with async_timeout.timeout(1.0, loop=self.light.hass.loop):
|
||||
yield from self.event.wait()
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
yield from self.event.wait()
|
||||
|
||||
return self.message
|
||||
|
||||
@ -209,17 +355,13 @@ def convert_16_to_8(value):
|
||||
class LIFXLight(Light):
|
||||
"""Representation of a LIFX light."""
|
||||
|
||||
def __init__(self, device):
|
||||
def __init__(self, device, effects_conductor):
|
||||
"""Initialize the light."""
|
||||
self.device = device
|
||||
self.effects_conductor = effects_conductor
|
||||
self.registered = True
|
||||
self.product = device.product
|
||||
self.blocker = None
|
||||
self.effect_data = None
|
||||
self.postponed_update = None
|
||||
self._name = device.label
|
||||
self.set_power(device.power_level)
|
||||
self.set_color(*device.color)
|
||||
|
||||
@property
|
||||
def lifxwhite(self):
|
||||
@ -235,7 +377,7 @@ class LIFXLight(Light):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
return self.device.label
|
||||
|
||||
@property
|
||||
def who(self):
|
||||
@ -248,21 +390,23 @@ class LIFXLight(Light):
|
||||
@property
|
||||
def rgb_color(self):
|
||||
"""Return the RGB value."""
|
||||
_LOGGER.debug(
|
||||
"rgb_color: [%d %d %d]", self._rgb[0], self._rgb[1], self._rgb[2])
|
||||
return self._rgb
|
||||
hue, sat, bri, _ = self.device.color
|
||||
|
||||
return color_util.color_hsv_to_RGB(
|
||||
hue, convert_16_to_8(sat), convert_16_to_8(bri))
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
brightness = convert_16_to_8(self._bri)
|
||||
brightness = convert_16_to_8(self.device.color[2])
|
||||
_LOGGER.debug("brightness: %d", brightness)
|
||||
return brightness
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the color temperature."""
|
||||
temperature = color_util.color_temperature_kelvin_to_mired(self._kel)
|
||||
kelvin = self.device.color[3]
|
||||
temperature = color_util.color_temperature_kelvin_to_mired(kelvin)
|
||||
|
||||
_LOGGER.debug("color_temp: %d", temperature)
|
||||
return temperature
|
||||
@ -290,13 +434,15 @@ class LIFXLight(Light):
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
_LOGGER.debug("is_on: %d", self._power)
|
||||
return self._power != 0
|
||||
return self.device.power_level != 0
|
||||
|
||||
@property
|
||||
def effect(self):
|
||||
"""Return the currently running effect."""
|
||||
return self.effect_data.effect.name if self.effect_data else None
|
||||
"""Return the name of the currently running effect."""
|
||||
effect = self.effects_conductor.effect(self.device)
|
||||
if effect:
|
||||
return 'lifx_effect_' + effect.name
|
||||
return None
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
@ -311,38 +457,35 @@ class LIFXLight(Light):
|
||||
|
||||
@property
|
||||
def effect_list(self):
|
||||
"""Return the list of supported effects."""
|
||||
return lifx_effects.effect_list(self)
|
||||
"""Return the list of supported effects for this light."""
|
||||
if self.lifxwhite:
|
||||
return [
|
||||
SERVICE_EFFECT_PULSE,
|
||||
SERVICE_EFFECT_STOP,
|
||||
]
|
||||
|
||||
return [
|
||||
SERVICE_EFFECT_COLORLOOP,
|
||||
SERVICE_EFFECT_PULSE,
|
||||
SERVICE_EFFECT_STOP,
|
||||
]
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_after_transition(self, now):
|
||||
"""Request new status after completion of the last transition."""
|
||||
self.postponed_update = None
|
||||
yield from self.refresh_state()
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def unblock_updates(self, now):
|
||||
"""Allow async_update after the new state has settled on the bulb."""
|
||||
self.blocker = None
|
||||
yield from self.refresh_state()
|
||||
yield from self.async_update()
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
def update_later(self, when):
|
||||
"""Block immediate update requests and schedule one for later."""
|
||||
if self.blocker:
|
||||
self.blocker()
|
||||
self.blocker = async_track_point_in_utc_time(
|
||||
self.hass, self.unblock_updates,
|
||||
util.dt.utcnow() + timedelta(milliseconds=BULB_LATENCY))
|
||||
|
||||
"""Schedule an update requests when a transition is over."""
|
||||
if self.postponed_update:
|
||||
self.postponed_update()
|
||||
self.postponed_update = None
|
||||
if when > BULB_LATENCY:
|
||||
if when > 0:
|
||||
self.postponed_update = async_track_point_in_utc_time(
|
||||
self.hass, self.update_after_transition,
|
||||
util.dt.utcnow() + timedelta(milliseconds=when+BULB_LATENCY))
|
||||
util.dt.utcnow() + timedelta(milliseconds=when))
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs):
|
||||
@ -359,10 +502,10 @@ class LIFXLight(Light):
|
||||
@asyncio.coroutine
|
||||
def async_set_state(self, **kwargs):
|
||||
"""Set a color on the light and turn it on/off."""
|
||||
yield from self.stop_effect()
|
||||
yield from self.effects_conductor.stop([self.device])
|
||||
|
||||
if ATTR_EFFECT in kwargs:
|
||||
yield from lifx_effects.default_effect(self, **kwargs)
|
||||
yield from self.default_effect(**kwargs)
|
||||
return
|
||||
|
||||
if ATTR_INFRARED in kwargs:
|
||||
@ -377,124 +520,45 @@ class LIFXLight(Light):
|
||||
power_on = kwargs.get(ATTR_POWER, False)
|
||||
power_off = not kwargs.get(ATTR_POWER, True)
|
||||
|
||||
hsbk, changed_color = self.find_hsbk(**kwargs)
|
||||
_LOGGER.debug("turn_on: %s (%d) %d %d %d %d %d",
|
||||
self.who, self._power, fade, *hsbk)
|
||||
hsbk = merge_hsbk(self.device.color, find_hsbk(**kwargs))
|
||||
|
||||
if self._power == 0:
|
||||
# Send messages, waiting for ACK each time
|
||||
ack = AwaitAioLIFX(self).wait
|
||||
bulb = self.device
|
||||
|
||||
if not self.is_on:
|
||||
if power_off:
|
||||
self.device.set_power(False, None, 0)
|
||||
if changed_color:
|
||||
self.device.set_color(hsbk, None, 0)
|
||||
yield from ack(partial(bulb.set_power, False))
|
||||
if hsbk:
|
||||
yield from ack(partial(bulb.set_color, hsbk))
|
||||
if power_on:
|
||||
self.device.set_power(True, None, fade)
|
||||
yield from ack(partial(bulb.set_power, True, duration=fade))
|
||||
else:
|
||||
if power_on:
|
||||
self.device.set_power(True, None, 0)
|
||||
if changed_color:
|
||||
self.device.set_color(hsbk, None, fade)
|
||||
yield from ack(partial(bulb.set_power, True))
|
||||
if hsbk:
|
||||
yield from ack(partial(bulb.set_color, hsbk, duration=fade))
|
||||
if power_off:
|
||||
self.device.set_power(False, None, fade)
|
||||
yield from ack(partial(bulb.set_power, False, duration=fade))
|
||||
|
||||
if power_on:
|
||||
self.update_later(0)
|
||||
else:
|
||||
self.update_later(fade)
|
||||
# Avoid state ping-pong by holding off updates while the state settles
|
||||
yield from asyncio.sleep(0.25)
|
||||
|
||||
if fade <= BULB_LATENCY:
|
||||
if power_on:
|
||||
self.set_power(1)
|
||||
if power_off:
|
||||
self.set_power(0)
|
||||
if changed_color:
|
||||
self.set_color(*hsbk)
|
||||
# Schedule an update when the transition is complete
|
||||
self.update_later(fade)
|
||||
|
||||
@asyncio.coroutine
|
||||
def default_effect(self, **kwargs):
|
||||
"""Start an effect with default parameters."""
|
||||
service = kwargs[ATTR_EFFECT]
|
||||
data = {
|
||||
ATTR_ENTITY_ID: self.entity_id,
|
||||
}
|
||||
yield from self.hass.services.async_call(DOMAIN, service, data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
"""Update bulb status (if it is available)."""
|
||||
"""Update bulb status."""
|
||||
_LOGGER.debug("%s async_update", self.who)
|
||||
if self.blocker is None:
|
||||
yield from self.refresh_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop_effect(self):
|
||||
"""Stop the currently running effect (if any)."""
|
||||
if self.effect_data:
|
||||
yield from self.effect_data.effect.async_restore(self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def refresh_state(self):
|
||||
"""Ask the device about its current state and update our copy."""
|
||||
if self.available:
|
||||
msg = yield from AwaitAioLIFX(self).wait(self.device.get_color)
|
||||
if msg is not None:
|
||||
self.set_power(self.device.power_level)
|
||||
self.set_color(*self.device.color)
|
||||
self._name = self.device.label
|
||||
|
||||
def find_hsbk(self, **kwargs):
|
||||
"""Find the desired color from a number of possible inputs."""
|
||||
changed_color = False
|
||||
|
||||
hsbk = kwargs.pop(ATTR_HSBK, None)
|
||||
if hsbk is not None:
|
||||
return [hsbk, True]
|
||||
|
||||
preprocess_turn_on_alternatives(kwargs)
|
||||
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
hue, saturation, brightness = \
|
||||
color_util.color_RGB_to_hsv(*kwargs[ATTR_RGB_COLOR])
|
||||
saturation = convert_8_to_16(saturation)
|
||||
brightness = convert_8_to_16(brightness)
|
||||
changed_color = True
|
||||
else:
|
||||
hue = self._hue
|
||||
saturation = self._sat
|
||||
brightness = self._bri
|
||||
|
||||
if ATTR_XY_COLOR in kwargs:
|
||||
hue, saturation = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR])
|
||||
saturation = convert_8_to_16(saturation)
|
||||
changed_color = True
|
||||
|
||||
# When color or temperature is set, use a default value for the other
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
kelvin = int(color_util.color_temperature_mired_to_kelvin(
|
||||
kwargs[ATTR_COLOR_TEMP]))
|
||||
if not changed_color:
|
||||
saturation = 0
|
||||
changed_color = True
|
||||
else:
|
||||
if changed_color:
|
||||
kelvin = 3500
|
||||
else:
|
||||
kelvin = self._kel
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = convert_8_to_16(kwargs[ATTR_BRIGHTNESS])
|
||||
changed_color = True
|
||||
else:
|
||||
brightness = self._bri
|
||||
|
||||
return [[hue, saturation, brightness, kelvin], changed_color]
|
||||
|
||||
def set_power(self, power):
|
||||
"""Set power state value."""
|
||||
_LOGGER.debug("set_power: %d", power)
|
||||
self._power = (power != 0)
|
||||
|
||||
def set_color(self, hue, sat, bri, kel):
|
||||
"""Set color state values."""
|
||||
self._hue = hue
|
||||
self._sat = sat
|
||||
self._bri = bri
|
||||
self._kel = kel
|
||||
|
||||
red, green, blue = color_util.color_hsv_to_RGB(
|
||||
hue, convert_16_to_8(sat), convert_16_to_8(bri))
|
||||
|
||||
_LOGGER.debug("set_color: %d %d %d %d [%d %d %d]",
|
||||
hue, sat, bri, kel, red, green, blue)
|
||||
|
||||
self._rgb = [red, green, blue]
|
||||
yield from AwaitAioLIFX(self).wait(self.device.get_color)
|
@ -1,388 +0,0 @@
|
||||
"""Support for light effects for the LIFX light platform."""
|
||||
import logging
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (
|
||||
DOMAIN, ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME,
|
||||
ATTR_RGB_COLOR, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_EFFECT, ATTR_TRANSITION,
|
||||
VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT)
|
||||
from homeassistant.const import (ATTR_ENTITY_ID)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_EFFECT_BREATHE = 'lifx_effect_breathe'
|
||||
SERVICE_EFFECT_PULSE = 'lifx_effect_pulse'
|
||||
SERVICE_EFFECT_COLORLOOP = 'lifx_effect_colorloop'
|
||||
SERVICE_EFFECT_STOP = 'lifx_effect_stop'
|
||||
|
||||
ATTR_POWER_ON = 'power_on'
|
||||
ATTR_PERIOD = 'period'
|
||||
ATTR_CYCLES = 'cycles'
|
||||
ATTR_MODE = 'mode'
|
||||
ATTR_SPREAD = 'spread'
|
||||
ATTR_CHANGE = 'change'
|
||||
|
||||
MODE_BLINK = 'blink'
|
||||
MODE_BREATHE = 'breathe'
|
||||
MODE_PING = 'ping'
|
||||
MODE_STROBE = 'strobe'
|
||||
MODE_SOLID = 'solid'
|
||||
|
||||
MODES = [MODE_BLINK, MODE_BREATHE, MODE_PING, MODE_STROBE, MODE_SOLID]
|
||||
|
||||
# aiolifx waveform modes
|
||||
WAVEFORM_SINE = 1
|
||||
WAVEFORM_PULSE = 4
|
||||
|
||||
NEUTRAL_WHITE = 3500
|
||||
|
||||
LIFX_EFFECT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(ATTR_POWER_ON, default=True): cv.boolean,
|
||||
})
|
||||
|
||||
LIFX_EFFECT_BREATHE_SCHEMA = LIFX_EFFECT_SCHEMA.extend({
|
||||
ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT,
|
||||
ATTR_COLOR_NAME: cv.string,
|
||||
ATTR_RGB_COLOR: vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)),
|
||||
vol.Coerce(tuple)),
|
||||
ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
ATTR_KELVIN: vol.All(vol.Coerce(int), vol.Range(min=0)),
|
||||
ATTR_PERIOD: vol.All(vol.Coerce(float), vol.Range(min=0.05)),
|
||||
ATTR_CYCLES: vol.All(vol.Coerce(float), vol.Range(min=1)),
|
||||
})
|
||||
|
||||
LIFX_EFFECT_PULSE_SCHEMA = LIFX_EFFECT_BREATHE_SCHEMA.extend({
|
||||
vol.Optional(ATTR_MODE, default=MODE_BLINK): vol.In(MODES),
|
||||
})
|
||||
|
||||
LIFX_EFFECT_COLORLOOP_SCHEMA = LIFX_EFFECT_SCHEMA.extend({
|
||||
ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT,
|
||||
vol.Optional(ATTR_PERIOD, default=60):
|
||||
vol.All(vol.Coerce(float), vol.Clamp(min=0.05)),
|
||||
vol.Optional(ATTR_CHANGE, default=20):
|
||||
vol.All(vol.Coerce(float), vol.Clamp(min=0, max=360)),
|
||||
vol.Optional(ATTR_SPREAD, default=30):
|
||||
vol.All(vol.Coerce(float), vol.Clamp(min=0, max=360)),
|
||||
ATTR_TRANSITION: vol.All(vol.Coerce(float), vol.Range(min=0)),
|
||||
})
|
||||
|
||||
LIFX_EFFECT_STOP_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(ATTR_POWER_ON, default=False): cv.boolean,
|
||||
})
|
||||
|
||||
|
||||
def setup(hass, lifx_manager):
|
||||
"""Register the LIFX effects as hass service calls."""
|
||||
@asyncio.coroutine
|
||||
def async_service_handle(service):
|
||||
"""Apply a service."""
|
||||
entities = lifx_manager.service_to_entities(service)
|
||||
if entities:
|
||||
yield from start_effect(hass, entities,
|
||||
service.service, **service.data)
|
||||
|
||||
descriptions = lifx_manager.get_descriptions()
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_EFFECT_BREATHE, async_service_handle,
|
||||
descriptions.get(SERVICE_EFFECT_BREATHE),
|
||||
schema=LIFX_EFFECT_BREATHE_SCHEMA)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_EFFECT_PULSE, async_service_handle,
|
||||
descriptions.get(SERVICE_EFFECT_PULSE),
|
||||
schema=LIFX_EFFECT_PULSE_SCHEMA)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_EFFECT_COLORLOOP, async_service_handle,
|
||||
descriptions.get(SERVICE_EFFECT_COLORLOOP),
|
||||
schema=LIFX_EFFECT_COLORLOOP_SCHEMA)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_EFFECT_STOP, async_service_handle,
|
||||
descriptions.get(SERVICE_EFFECT_STOP),
|
||||
schema=LIFX_EFFECT_STOP_SCHEMA)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_effect(hass, devices, service, **data):
|
||||
"""Start a light effect."""
|
||||
tasks = []
|
||||
for light in devices:
|
||||
tasks.append(hass.async_add_job(light.stop_effect()))
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
if service in SERVICE_EFFECT_BREATHE:
|
||||
effect = LIFXEffectBreathe(hass, devices)
|
||||
elif service in SERVICE_EFFECT_PULSE:
|
||||
effect = LIFXEffectPulse(hass, devices)
|
||||
elif service == SERVICE_EFFECT_COLORLOOP:
|
||||
effect = LIFXEffectColorloop(hass, devices)
|
||||
elif service == SERVICE_EFFECT_STOP:
|
||||
effect = LIFXEffectStop(hass, devices)
|
||||
|
||||
hass.async_add_job(effect.async_perform(**data))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def default_effect(light, **kwargs):
|
||||
"""Start an effect with default parameters."""
|
||||
service = kwargs[ATTR_EFFECT]
|
||||
data = {
|
||||
ATTR_ENTITY_ID: light.entity_id,
|
||||
}
|
||||
yield from light.hass.services.async_call(DOMAIN, service, data)
|
||||
|
||||
|
||||
def effect_list(light):
|
||||
"""Return the list of supported effects for this light."""
|
||||
if light.lifxwhite:
|
||||
return [
|
||||
SERVICE_EFFECT_BREATHE,
|
||||
SERVICE_EFFECT_PULSE,
|
||||
SERVICE_EFFECT_STOP,
|
||||
]
|
||||
else:
|
||||
return [
|
||||
SERVICE_EFFECT_COLORLOOP,
|
||||
SERVICE_EFFECT_BREATHE,
|
||||
SERVICE_EFFECT_PULSE,
|
||||
SERVICE_EFFECT_STOP,
|
||||
]
|
||||
|
||||
|
||||
class LIFXEffectData(object):
|
||||
"""Structure describing a running effect."""
|
||||
|
||||
def __init__(self, effect, power, color):
|
||||
"""Initialize data structure."""
|
||||
self.effect = effect
|
||||
self.power = power
|
||||
self.color = color
|
||||
|
||||
|
||||
class LIFXEffect(object):
|
||||
"""Representation of a light effect running on a number of lights."""
|
||||
|
||||
def __init__(self, hass, lights):
|
||||
"""Initialize the effect."""
|
||||
self.hass = hass
|
||||
self.lights = lights
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_perform(self, **kwargs):
|
||||
"""Do common setup and play the effect."""
|
||||
yield from self.async_setup(**kwargs)
|
||||
yield from self.async_play(**kwargs)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(self, **kwargs):
|
||||
"""Prepare all lights for the effect."""
|
||||
for light in self.lights:
|
||||
# Remember the current state (as far as we know it)
|
||||
yield from light.refresh_state()
|
||||
light.effect_data = LIFXEffectData(
|
||||
self, light.is_on, light.device.color)
|
||||
|
||||
# Temporarily turn on power for the effect to be visible
|
||||
if kwargs[ATTR_POWER_ON] and not light.is_on:
|
||||
hsbk = self.from_poweroff_hsbk(light, **kwargs)
|
||||
light.device.set_color(hsbk)
|
||||
light.device.set_power(True)
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@asyncio.coroutine
|
||||
def async_play(self, **kwargs):
|
||||
"""Play the effect."""
|
||||
yield None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_restore(self, light):
|
||||
"""Restore to the original state (if we are still running)."""
|
||||
if light in self.lights:
|
||||
self.lights.remove(light)
|
||||
|
||||
if light.effect_data and light.effect_data.effect == self:
|
||||
if not light.effect_data.power:
|
||||
light.device.set_power(False)
|
||||
yield from asyncio.sleep(0.5)
|
||||
|
||||
light.device.set_color(light.effect_data.color)
|
||||
yield from asyncio.sleep(0.5)
|
||||
|
||||
light.effect_data = None
|
||||
yield from light.refresh_state()
|
||||
|
||||
def from_poweroff_hsbk(self, light, **kwargs):
|
||||
"""Return the color when starting from a powered off state."""
|
||||
return [random.randint(0, 65535), 65535, 0, NEUTRAL_WHITE]
|
||||
|
||||
|
||||
class LIFXEffectPulse(LIFXEffect):
|
||||
"""Representation of a pulse effect."""
|
||||
|
||||
def __init__(self, hass, lights):
|
||||
"""Initialize the pulse effect."""
|
||||
super().__init__(hass, lights)
|
||||
self.name = SERVICE_EFFECT_PULSE
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_play(self, **kwargs):
|
||||
"""Play the effect on all lights."""
|
||||
for light in self.lights:
|
||||
self.hass.async_add_job(self.async_light_play(light, **kwargs))
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_light_play(self, light, **kwargs):
|
||||
"""Play a light effect on the bulb."""
|
||||
hsbk, color_changed = light.find_hsbk(**kwargs)
|
||||
|
||||
if kwargs[ATTR_MODE] == MODE_STROBE:
|
||||
# Strobe must flash from a dark color
|
||||
light.device.set_color([0, 0, 0, NEUTRAL_WHITE])
|
||||
yield from asyncio.sleep(0.1)
|
||||
default_period = 0.1
|
||||
default_cycles = 10
|
||||
else:
|
||||
default_period = 1.0
|
||||
default_cycles = 1
|
||||
|
||||
period = kwargs.get(ATTR_PERIOD, default_period)
|
||||
cycles = kwargs.get(ATTR_CYCLES, default_cycles)
|
||||
|
||||
# Breathe has a special waveform
|
||||
if kwargs[ATTR_MODE] == MODE_BREATHE:
|
||||
waveform = WAVEFORM_SINE
|
||||
else:
|
||||
waveform = WAVEFORM_PULSE
|
||||
|
||||
# Ping and solid have special duty cycles
|
||||
if kwargs[ATTR_MODE] == MODE_PING:
|
||||
ping_duration = int(5000 - min(2500, 300*period))
|
||||
duty_cycle = 2**15 - ping_duration
|
||||
elif kwargs[ATTR_MODE] == MODE_SOLID:
|
||||
duty_cycle = -2**15
|
||||
else:
|
||||
duty_cycle = 0
|
||||
|
||||
# Set default effect color based on current setting
|
||||
if not color_changed:
|
||||
if kwargs[ATTR_MODE] == MODE_STROBE:
|
||||
# Strobe: cold white
|
||||
hsbk = [hsbk[0], 0, 65535, 5600]
|
||||
elif light.lifxwhite or hsbk[1] < 65536/2:
|
||||
# White: toggle brightness
|
||||
hsbk[2] = 65535 if hsbk[2] < 65536/2 else 0
|
||||
else:
|
||||
# Color: fully desaturate with full brightness
|
||||
hsbk = [hsbk[0], 0, 65535, 4000]
|
||||
|
||||
# Start the effect
|
||||
args = {
|
||||
'transient': 1,
|
||||
'color': hsbk,
|
||||
'period': int(period*1000),
|
||||
'cycles': cycles,
|
||||
'duty_cycle': duty_cycle,
|
||||
'waveform': waveform,
|
||||
}
|
||||
light.device.set_waveform(args)
|
||||
|
||||
# Wait for completion and restore the initial state
|
||||
yield from asyncio.sleep(period*cycles)
|
||||
yield from self.async_restore(light)
|
||||
|
||||
def from_poweroff_hsbk(self, light, **kwargs):
|
||||
"""Return the color is the target color, but no brightness."""
|
||||
hsbk, _ = light.find_hsbk(**kwargs)
|
||||
return [hsbk[0], hsbk[1], 0, hsbk[2]]
|
||||
|
||||
|
||||
class LIFXEffectBreathe(LIFXEffectPulse):
|
||||
"""Representation of a breathe effect."""
|
||||
|
||||
def __init__(self, hass, lights):
|
||||
"""Initialize the breathe effect."""
|
||||
super().__init__(hass, lights)
|
||||
self.name = SERVICE_EFFECT_BREATHE
|
||||
_LOGGER.warning("'lifx_effect_breathe' is deprecated. Please use "
|
||||
"'lifx_effect_pulse' with 'mode: breathe'")
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_perform(self, **kwargs):
|
||||
"""Prepare all lights for the effect."""
|
||||
kwargs[ATTR_MODE] = MODE_BREATHE
|
||||
yield from super().async_perform(**kwargs)
|
||||
|
||||
|
||||
class LIFXEffectColorloop(LIFXEffect):
|
||||
"""Representation of a colorloop effect."""
|
||||
|
||||
def __init__(self, hass, lights):
|
||||
"""Initialize the colorloop effect."""
|
||||
super().__init__(hass, lights)
|
||||
self.name = SERVICE_EFFECT_COLORLOOP
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_play(self, **kwargs):
|
||||
"""Play the effect on all lights."""
|
||||
period = kwargs[ATTR_PERIOD]
|
||||
spread = kwargs[ATTR_SPREAD]
|
||||
change = kwargs[ATTR_CHANGE]
|
||||
direction = 1 if random.randint(0, 1) else -1
|
||||
|
||||
# Random start
|
||||
hue = random.uniform(0, 360) % 360
|
||||
|
||||
while self.lights:
|
||||
hue = (hue + direction*change) % 360
|
||||
|
||||
random.shuffle(self.lights)
|
||||
lhue = hue
|
||||
|
||||
for light in self.lights:
|
||||
if ATTR_TRANSITION in kwargs:
|
||||
transition = int(1000*kwargs[ATTR_TRANSITION])
|
||||
elif light == self.lights[0] or spread > 0:
|
||||
transition = int(1000 * random.uniform(period/2, period))
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = int(65535/255*kwargs[ATTR_BRIGHTNESS])
|
||||
else:
|
||||
brightness = light.effect_data.color[2]
|
||||
|
||||
hsbk = [
|
||||
int(65535/360*lhue),
|
||||
int(random.uniform(0.8, 1.0)*65535),
|
||||
brightness,
|
||||
NEUTRAL_WHITE,
|
||||
]
|
||||
light.device.set_color(hsbk, None, transition)
|
||||
|
||||
# Adjust the next light so the full spread is used
|
||||
if len(self.lights) > 1:
|
||||
lhue = (lhue + spread/(len(self.lights)-1)) % 360
|
||||
|
||||
yield from asyncio.sleep(period)
|
||||
|
||||
|
||||
class LIFXEffectStop(LIFXEffect):
|
||||
"""A no-op effect, but starting it will stop an existing effect."""
|
||||
|
||||
def __init__(self, hass, lights):
|
||||
"""Initialize the stop effect."""
|
||||
super().__init__(hass, lights)
|
||||
self.name = SERVICE_EFFECT_STOP
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_perform(self, **kwargs):
|
||||
"""Do nothing."""
|
||||
yield None
|
@ -1,98 +0,0 @@
|
||||
lifx_set_state:
|
||||
description: Set a color/brightness and possibliy turn the light on/off
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to set a state on
|
||||
example: 'light.garage'
|
||||
|
||||
'...':
|
||||
description: All turn_on parameters can be used to specify a color
|
||||
|
||||
infrared:
|
||||
description: Automatic infrared level (0..255) when light brightness is low
|
||||
example: 255
|
||||
|
||||
transition:
|
||||
description: Duration in seconds it takes to get to the final state
|
||||
example: 10
|
||||
|
||||
power:
|
||||
description: Turn the light on (True) or off (False). Leave out to keep the power as it is.
|
||||
example: True
|
||||
|
||||
|
||||
lifx_effect_breathe:
|
||||
description: Deprecated, use lifx_effect_pulse
|
||||
|
||||
lifx_effect_pulse:
|
||||
description: Run a flash effect by changing to a color and back.
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to run the effect on
|
||||
example: 'light.kitchen'
|
||||
|
||||
mode:
|
||||
description: 'Decides how colors are changed. Possible values: blink, breathe, ping, strobe, solid'
|
||||
example: strobe
|
||||
|
||||
brightness:
|
||||
description: Number between 0..255 indicating brightness of the temporary color
|
||||
example: 120
|
||||
|
||||
color_name:
|
||||
description: A human readable color name
|
||||
example: 'red'
|
||||
|
||||
rgb_color:
|
||||
description: The temporary color in RGB-format
|
||||
example: '[255, 100, 100]'
|
||||
|
||||
period:
|
||||
description: Duration of the effect in seconds (default 1.0)
|
||||
example: 3
|
||||
|
||||
cycles:
|
||||
description: Number of times the effect should run (default 1.0)
|
||||
example: 2
|
||||
|
||||
power_on:
|
||||
description: Powered off lights are temporarily turned on during the effect (default True)
|
||||
example: False
|
||||
|
||||
lifx_effect_colorloop:
|
||||
description: Run an effect with looping colors.
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to run the effect on
|
||||
example: 'light.disco1, light.disco2, light.disco3'
|
||||
|
||||
brightness:
|
||||
description: Number between 0 and 255 indicating brightness of the effect. Leave this out to maintain the current brightness of each participating light
|
||||
example: 120
|
||||
|
||||
period:
|
||||
description: Duration (in seconds) between color changes (default 60)
|
||||
example: 180
|
||||
|
||||
change:
|
||||
description: Hue movement per period, in degrees on a color wheel (ranges from 0 to 360, default 20)
|
||||
example: 45
|
||||
|
||||
spread:
|
||||
description: Maximum hue difference between participating lights, in degrees on a color wheel (ranges from 0 to 360, default 30)
|
||||
example: 0
|
||||
|
||||
power_on:
|
||||
description: Powered off lights are temporarily turned on during the effect (default True)
|
||||
example: False
|
||||
|
||||
lifx_effect_stop:
|
||||
description: Stop a running effect.
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to stop effects on. Leave out to stop effects everywhere.
|
||||
example: 'light.bedroom'
|
@ -101,3 +101,98 @@ hue_activate_scene:
|
||||
scene_name:
|
||||
description: Name of hue scene from the hue app
|
||||
example: "Energize"
|
||||
|
||||
lifx_set_state:
|
||||
description: Set a color/brightness and possibliy turn the light on/off
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to set a state on
|
||||
example: 'light.garage'
|
||||
|
||||
'...':
|
||||
description: All turn_on parameters can be used to specify a color
|
||||
|
||||
infrared:
|
||||
description: Automatic infrared level (0..255) when light brightness is low
|
||||
example: 255
|
||||
|
||||
transition:
|
||||
description: Duration in seconds it takes to get to the final state
|
||||
example: 10
|
||||
|
||||
power:
|
||||
description: Turn the light on (True) or off (False). Leave out to keep the power as it is.
|
||||
example: True
|
||||
|
||||
lifx_effect_pulse:
|
||||
description: Run a flash effect by changing to a color and back.
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to run the effect on
|
||||
example: 'light.kitchen'
|
||||
|
||||
mode:
|
||||
description: 'Decides how colors are changed. Possible values: blink, breathe, ping, strobe, solid'
|
||||
example: strobe
|
||||
|
||||
brightness:
|
||||
description: Number between 0..255 indicating brightness of the temporary color
|
||||
example: 120
|
||||
|
||||
color_name:
|
||||
description: A human readable color name
|
||||
example: 'red'
|
||||
|
||||
rgb_color:
|
||||
description: The temporary color in RGB-format
|
||||
example: '[255, 100, 100]'
|
||||
|
||||
period:
|
||||
description: Duration of the effect in seconds (default 1.0)
|
||||
example: 3
|
||||
|
||||
cycles:
|
||||
description: Number of times the effect should run (default 1.0)
|
||||
example: 2
|
||||
|
||||
power_on:
|
||||
description: Powered off lights are temporarily turned on during the effect (default True)
|
||||
example: False
|
||||
|
||||
lifx_effect_colorloop:
|
||||
description: Run an effect with looping colors.
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to run the effect on
|
||||
example: 'light.disco1, light.disco2, light.disco3'
|
||||
|
||||
brightness:
|
||||
description: Number between 0 and 255 indicating brightness of the effect. Leave this out to maintain the current brightness of each participating light
|
||||
example: 120
|
||||
|
||||
period:
|
||||
description: Duration (in seconds) between color changes (default 60)
|
||||
example: 180
|
||||
|
||||
change:
|
||||
description: Hue movement per period, in degrees on a color wheel (ranges from 0 to 360, default 20)
|
||||
example: 45
|
||||
|
||||
spread:
|
||||
description: Maximum hue difference between participating lights, in degrees on a color wheel (ranges from 0 to 360, default 30)
|
||||
example: 0
|
||||
|
||||
power_on:
|
||||
description: Powered off lights are temporarily turned on during the effect (default True)
|
||||
example: False
|
||||
|
||||
lifx_effect_stop:
|
||||
description: Stop a running effect.
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to stop effects on. Leave out to stop effects everywhere.
|
||||
example: 'light.bedroom'
|
||||
|
@ -49,7 +49,10 @@ aiodns==1.1.1
|
||||
aiohttp_cors==0.5.3
|
||||
|
||||
# homeassistant.components.light.lifx
|
||||
aiolifx==0.4.8
|
||||
aiolifx==0.5.0
|
||||
|
||||
# homeassistant.components.light.lifx
|
||||
aiolifx_effects==0.1.0
|
||||
|
||||
# homeassistant.components.scene.hunterdouglas_powerview
|
||||
aiopvapi==1.4
|
||||
|
Loading…
x
Reference in New Issue
Block a user