mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +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/flux_led.py
|
||||||
homeassistant/components/light/hue.py
|
homeassistant/components/light/hue.py
|
||||||
homeassistant/components/light/hyperion.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/lifx_legacy.py
|
||||||
homeassistant/components/light/limitlessled.py
|
homeassistant/components/light/limitlessled.py
|
||||||
homeassistant/components/light/mystrom.py
|
homeassistant/components/light/mystrom.py
|
||||||
|
@ -11,18 +11,19 @@ import math
|
|||||||
from os import path
|
from os import path
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import async_timeout
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
Light, DOMAIN, PLATFORM_SCHEMA, LIGHT_TURN_ON_SCHEMA,
|
Light, DOMAIN, PLATFORM_SCHEMA, LIGHT_TURN_ON_SCHEMA,
|
||||||
ATTR_BRIGHTNESS, ATTR_RGB_COLOR,
|
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, ATTR_RGB_COLOR,
|
||||||
ATTR_XY_COLOR, ATTR_COLOR_TEMP, ATTR_TRANSITION, ATTR_EFFECT,
|
ATTR_XY_COLOR, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_TRANSITION, ATTR_EFFECT,
|
||||||
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR,
|
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR,
|
||||||
SUPPORT_XY_COLOR, SUPPORT_TRANSITION, SUPPORT_EFFECT,
|
SUPPORT_XY_COLOR, SUPPORT_TRANSITION, SUPPORT_EFFECT,
|
||||||
|
VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT,
|
||||||
preprocess_turn_on_alternatives)
|
preprocess_turn_on_alternatives)
|
||||||
from homeassistant.config import load_yaml_config_file
|
from homeassistant.config import load_yaml_config_file
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant import util
|
from homeassistant import util
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
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.helpers.config_validation as cv
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
from . import effects as lifx_effects
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
REQUIREMENTS = ['aiolifx==0.4.8']
|
REQUIREMENTS = ['aiolifx==0.5.0', 'aiolifx_effects==0.1.0']
|
||||||
|
|
||||||
UDP_BROADCAST_PORT = 56700
|
UDP_BROADCAST_PORT = 56700
|
||||||
|
|
||||||
# Delay (in ms) expected for changes to take effect in the physical bulb
|
|
||||||
BULB_LATENCY = 500
|
|
||||||
|
|
||||||
CONF_SERVER = 'server'
|
CONF_SERVER = 'server'
|
||||||
|
|
||||||
SERVICE_LIFX_SET_STATE = 'lifx_set_state'
|
|
||||||
|
|
||||||
ATTR_HSBK = 'hsbk'
|
|
||||||
ATTR_INFRARED = 'infrared'
|
|
||||||
ATTR_POWER = 'power'
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Optional(CONF_SERVER, default='0.0.0.0'): cv.string,
|
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({
|
LIFX_SET_STATE_SCHEMA = LIGHT_TURN_ON_SCHEMA.extend({
|
||||||
ATTR_INFRARED: vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)),
|
ATTR_INFRARED: vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)),
|
||||||
ATTR_POWER: cv.boolean,
|
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
|
@asyncio.coroutine
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
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)
|
server_addr = config.get(CONF_SERVER)
|
||||||
|
|
||||||
lifx_manager = LIFXManager(hass, async_add_devices)
|
lifx_manager = LIFXManager(hass, async_add_devices)
|
||||||
|
lifx_discovery = aiolifx.LifxDiscovery(hass.loop, lifx_manager)
|
||||||
|
|
||||||
coro = hass.loop.create_datagram_endpoint(
|
coro = hass.loop.create_datagram_endpoint(
|
||||||
partial(aiolifx.LifxDiscovery, hass.loop, lifx_manager),
|
lambda: lifx_discovery, local_addr=(server_addr, UDP_BROADCAST_PORT))
|
||||||
local_addr=(server_addr, UDP_BROADCAST_PORT))
|
|
||||||
|
|
||||||
hass.async_add_job(coro)
|
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
|
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):
|
class LIFXManager(object):
|
||||||
"""Representation of all known LIFX entities."""
|
"""Representation of all known LIFX entities."""
|
||||||
|
|
||||||
def __init__(self, hass, async_add_devices):
|
def __init__(self, hass, async_add_devices):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
|
import aiolifx_effects
|
||||||
self.entities = {}
|
self.entities = {}
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.async_add_devices = async_add_devices
|
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
|
@asyncio.coroutine
|
||||||
def async_service_handle(service):
|
def async_service_handle(service):
|
||||||
"""Apply a service."""
|
"""Apply a service."""
|
||||||
@ -99,22 +197,73 @@ class LIFXManager(object):
|
|||||||
for light in self.service_to_entities(service):
|
for light in self.service_to_entities(service):
|
||||||
if service.service == SERVICE_LIFX_SET_STATE:
|
if service.service == SERVICE_LIFX_SET_STATE:
|
||||||
task = light.async_set_state(**service.data)
|
task = light.async_set_state(**service.data)
|
||||||
tasks.append(hass.async_add_job(task))
|
tasks.append(self.hass.async_add_job(task))
|
||||||
if tasks:
|
if tasks:
|
||||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
yield from asyncio.wait(tasks, loop=self.hass.loop)
|
||||||
|
|
||||||
descriptions = self.get_descriptions()
|
self.hass.services.async_register(
|
||||||
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN, SERVICE_LIFX_SET_STATE, async_service_handle,
|
DOMAIN, SERVICE_LIFX_SET_STATE, async_service_handle,
|
||||||
descriptions.get(SERVICE_LIFX_SET_STATE),
|
descriptions.get(SERVICE_LIFX_SET_STATE),
|
||||||
schema=LIFX_SET_STATE_SCHEMA)
|
schema=LIFX_SET_STATE_SCHEMA)
|
||||||
|
|
||||||
@staticmethod
|
def register_effects(self, descriptions):
|
||||||
def get_descriptions():
|
"""Register the LIFX effects as hass service calls."""
|
||||||
"""Load and return descriptions for our own service calls."""
|
@asyncio.coroutine
|
||||||
return load_yaml_config_file(
|
def async_service_handle(service):
|
||||||
path.join(path.dirname(__file__), 'services.yaml'))
|
"""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):
|
def service_to_entities(self, service):
|
||||||
"""Return the known devices that a service call mentions."""
|
"""Return the known devices that a service call mentions."""
|
||||||
@ -148,7 +297,7 @@ class LIFXManager(object):
|
|||||||
@callback
|
@callback
|
||||||
def ready(self, device, msg):
|
def ready(self, device, msg):
|
||||||
"""Handle the device once all data is retrieved."""
|
"""Handle the device once all data is retrieved."""
|
||||||
entity = LIFXLight(device)
|
entity = LIFXLight(device, self.effects_conductor)
|
||||||
_LOGGER.debug("%s register READY", entity.who)
|
_LOGGER.debug("%s register READY", entity.who)
|
||||||
self.entities[device.mac_addr] = entity
|
self.entities[device.mac_addr] = entity
|
||||||
self.async_add_devices([entity])
|
self.async_add_devices([entity])
|
||||||
@ -182,16 +331,13 @@ class AwaitAioLIFX:
|
|||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def wait(self, method):
|
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()
|
self.event.clear()
|
||||||
method(self.callback)
|
method(self.callback)
|
||||||
|
|
||||||
while self.light.available and not self.event.is_set():
|
yield from self.event.wait()
|
||||||
try:
|
|
||||||
with async_timeout.timeout(1.0, loop=self.light.hass.loop):
|
|
||||||
yield from self.event.wait()
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
@ -209,17 +355,13 @@ def convert_16_to_8(value):
|
|||||||
class LIFXLight(Light):
|
class LIFXLight(Light):
|
||||||
"""Representation of a LIFX light."""
|
"""Representation of a LIFX light."""
|
||||||
|
|
||||||
def __init__(self, device):
|
def __init__(self, device, effects_conductor):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self.device = device
|
self.device = device
|
||||||
|
self.effects_conductor = effects_conductor
|
||||||
self.registered = True
|
self.registered = True
|
||||||
self.product = device.product
|
self.product = device.product
|
||||||
self.blocker = None
|
|
||||||
self.effect_data = None
|
|
||||||
self.postponed_update = None
|
self.postponed_update = None
|
||||||
self._name = device.label
|
|
||||||
self.set_power(device.power_level)
|
|
||||||
self.set_color(*device.color)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lifxwhite(self):
|
def lifxwhite(self):
|
||||||
@ -235,7 +377,7 @@ class LIFXLight(Light):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return self._name
|
return self.device.label
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def who(self):
|
def who(self):
|
||||||
@ -248,21 +390,23 @@ class LIFXLight(Light):
|
|||||||
@property
|
@property
|
||||||
def rgb_color(self):
|
def rgb_color(self):
|
||||||
"""Return the RGB value."""
|
"""Return the RGB value."""
|
||||||
_LOGGER.debug(
|
hue, sat, bri, _ = self.device.color
|
||||||
"rgb_color: [%d %d %d]", self._rgb[0], self._rgb[1], self._rgb[2])
|
|
||||||
return self._rgb
|
return color_util.color_hsv_to_RGB(
|
||||||
|
hue, convert_16_to_8(sat), convert_16_to_8(bri))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""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)
|
_LOGGER.debug("brightness: %d", brightness)
|
||||||
return brightness
|
return brightness
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_temp(self):
|
def color_temp(self):
|
||||||
"""Return the color temperature."""
|
"""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)
|
_LOGGER.debug("color_temp: %d", temperature)
|
||||||
return temperature
|
return temperature
|
||||||
@ -290,13 +434,15 @@ class LIFXLight(Light):
|
|||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
_LOGGER.debug("is_on: %d", self._power)
|
return self.device.power_level != 0
|
||||||
return self._power != 0
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def effect(self):
|
def effect(self):
|
||||||
"""Return the currently running effect."""
|
"""Return the name of the currently running effect."""
|
||||||
return self.effect_data.effect.name if self.effect_data else None
|
effect = self.effects_conductor.effect(self.device)
|
||||||
|
if effect:
|
||||||
|
return 'lifx_effect_' + effect.name
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@ -311,38 +457,35 @@ class LIFXLight(Light):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def effect_list(self):
|
def effect_list(self):
|
||||||
"""Return the list of supported effects."""
|
"""Return the list of supported effects for this light."""
|
||||||
return lifx_effects.effect_list(self)
|
if self.lifxwhite:
|
||||||
|
return [
|
||||||
|
SERVICE_EFFECT_PULSE,
|
||||||
|
SERVICE_EFFECT_STOP,
|
||||||
|
]
|
||||||
|
|
||||||
|
return [
|
||||||
|
SERVICE_EFFECT_COLORLOOP,
|
||||||
|
SERVICE_EFFECT_PULSE,
|
||||||
|
SERVICE_EFFECT_STOP,
|
||||||
|
]
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def update_after_transition(self, now):
|
def update_after_transition(self, now):
|
||||||
"""Request new status after completion of the last transition."""
|
"""Request new status after completion of the last transition."""
|
||||||
self.postponed_update = None
|
self.postponed_update = None
|
||||||
yield from self.refresh_state()
|
yield from self.async_update()
|
||||||
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_ha_state()
|
yield from self.async_update_ha_state()
|
||||||
|
|
||||||
def update_later(self, when):
|
def update_later(self, when):
|
||||||
"""Block immediate update requests and schedule one for later."""
|
"""Schedule an update requests when a transition is over."""
|
||||||
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))
|
|
||||||
|
|
||||||
if self.postponed_update:
|
if self.postponed_update:
|
||||||
self.postponed_update()
|
self.postponed_update()
|
||||||
self.postponed_update = None
|
self.postponed_update = None
|
||||||
if when > BULB_LATENCY:
|
if when > 0:
|
||||||
self.postponed_update = async_track_point_in_utc_time(
|
self.postponed_update = async_track_point_in_utc_time(
|
||||||
self.hass, self.update_after_transition,
|
self.hass, self.update_after_transition,
|
||||||
util.dt.utcnow() + timedelta(milliseconds=when+BULB_LATENCY))
|
util.dt.utcnow() + timedelta(milliseconds=when))
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_turn_on(self, **kwargs):
|
def async_turn_on(self, **kwargs):
|
||||||
@ -359,10 +502,10 @@ class LIFXLight(Light):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_set_state(self, **kwargs):
|
def async_set_state(self, **kwargs):
|
||||||
"""Set a color on the light and turn it on/off."""
|
"""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:
|
if ATTR_EFFECT in kwargs:
|
||||||
yield from lifx_effects.default_effect(self, **kwargs)
|
yield from self.default_effect(**kwargs)
|
||||||
return
|
return
|
||||||
|
|
||||||
if ATTR_INFRARED in kwargs:
|
if ATTR_INFRARED in kwargs:
|
||||||
@ -377,124 +520,45 @@ class LIFXLight(Light):
|
|||||||
power_on = kwargs.get(ATTR_POWER, False)
|
power_on = kwargs.get(ATTR_POWER, False)
|
||||||
power_off = not kwargs.get(ATTR_POWER, True)
|
power_off = not kwargs.get(ATTR_POWER, True)
|
||||||
|
|
||||||
hsbk, changed_color = self.find_hsbk(**kwargs)
|
hsbk = merge_hsbk(self.device.color, find_hsbk(**kwargs))
|
||||||
_LOGGER.debug("turn_on: %s (%d) %d %d %d %d %d",
|
|
||||||
self.who, self._power, fade, *hsbk)
|
|
||||||
|
|
||||||
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:
|
if power_off:
|
||||||
self.device.set_power(False, None, 0)
|
yield from ack(partial(bulb.set_power, False))
|
||||||
if changed_color:
|
if hsbk:
|
||||||
self.device.set_color(hsbk, None, 0)
|
yield from ack(partial(bulb.set_color, hsbk))
|
||||||
if power_on:
|
if power_on:
|
||||||
self.device.set_power(True, None, fade)
|
yield from ack(partial(bulb.set_power, True, duration=fade))
|
||||||
else:
|
else:
|
||||||
if power_on:
|
if power_on:
|
||||||
self.device.set_power(True, None, 0)
|
yield from ack(partial(bulb.set_power, True))
|
||||||
if changed_color:
|
if hsbk:
|
||||||
self.device.set_color(hsbk, None, fade)
|
yield from ack(partial(bulb.set_color, hsbk, duration=fade))
|
||||||
if power_off:
|
if power_off:
|
||||||
self.device.set_power(False, None, fade)
|
yield from ack(partial(bulb.set_power, False, duration=fade))
|
||||||
|
|
||||||
if power_on:
|
# Avoid state ping-pong by holding off updates while the state settles
|
||||||
self.update_later(0)
|
yield from asyncio.sleep(0.25)
|
||||||
else:
|
|
||||||
self.update_later(fade)
|
|
||||||
|
|
||||||
if fade <= BULB_LATENCY:
|
# Schedule an update when the transition is complete
|
||||||
if power_on:
|
self.update_later(fade)
|
||||||
self.set_power(1)
|
|
||||||
if power_off:
|
@asyncio.coroutine
|
||||||
self.set_power(0)
|
def default_effect(self, **kwargs):
|
||||||
if changed_color:
|
"""Start an effect with default parameters."""
|
||||||
self.set_color(*hsbk)
|
service = kwargs[ATTR_EFFECT]
|
||||||
|
data = {
|
||||||
|
ATTR_ENTITY_ID: self.entity_id,
|
||||||
|
}
|
||||||
|
yield from self.hass.services.async_call(DOMAIN, service, data)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
def async_update(self):
|
||||||
"""Update bulb status (if it is available)."""
|
"""Update bulb status."""
|
||||||
_LOGGER.debug("%s async_update", self.who)
|
_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:
|
if self.available:
|
||||||
msg = yield from AwaitAioLIFX(self).wait(self.device.get_color)
|
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]
|
|
@ -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:
|
scene_name:
|
||||||
description: Name of hue scene from the hue app
|
description: Name of hue scene from the hue app
|
||||||
example: "Energize"
|
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
|
aiohttp_cors==0.5.3
|
||||||
|
|
||||||
# homeassistant.components.light.lifx
|
# 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
|
# homeassistant.components.scene.hunterdouglas_powerview
|
||||||
aiopvapi==1.4
|
aiopvapi==1.4
|
||||||
|
Loading…
x
Reference in New Issue
Block a user