2013-12-11 08:07:30 +00:00
|
|
|
"""
|
|
|
|
Provides functionality to interact with lights.
|
2014-03-26 07:08:50 +00:00
|
|
|
|
2015-11-09 12:12:18 +00:00
|
|
|
For more details about this component, please refer to the documentation at
|
|
|
|
https://home-assistant.io/components/light/
|
2013-12-11 08:07:30 +00:00
|
|
|
"""
|
2016-11-30 21:33:38 +00:00
|
|
|
import asyncio
|
2018-01-21 06:35:38 +00:00
|
|
|
import csv
|
2017-01-05 23:16:12 +00:00
|
|
|
from datetime import timedelta
|
2013-12-11 08:07:30 +00:00
|
|
|
import logging
|
2014-03-26 07:08:50 +00:00
|
|
|
import os
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2016-03-31 22:24:06 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-03-02 01:14:26 +00:00
|
|
|
from homeassistant.components.group import \
|
|
|
|
ENTITY_ID_FORMAT as GROUP_ENTITY_ID_FORMAT
|
2015-09-27 06:17:04 +00:00
|
|
|
from homeassistant.const import (
|
2018-01-21 06:35:38 +00:00
|
|
|
ATTR_ENTITY_ID, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
|
|
|
STATE_ON)
|
|
|
|
from homeassistant.core import callback
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
2015-06-13 23:42:09 +00:00
|
|
|
from homeassistant.helpers.entity import ToggleEntity
|
2015-09-27 06:17:04 +00:00
|
|
|
from homeassistant.helpers.entity_component import EntityComponent
|
2018-02-28 02:02:21 +00:00
|
|
|
from homeassistant.helpers import intent
|
2018-01-21 06:35:38 +00:00
|
|
|
from homeassistant.loader import bind_hass
|
2015-07-07 07:01:17 +00:00
|
|
|
import homeassistant.util.color as color_util
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2018-01-21 06:35:38 +00:00
|
|
|
DOMAIN = 'light'
|
2017-06-15 22:52:28 +00:00
|
|
|
DEPENDENCIES = ['group']
|
2017-01-05 23:16:12 +00:00
|
|
|
SCAN_INTERVAL = timedelta(seconds=30)
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2015-01-20 07:47:18 +00:00
|
|
|
GROUP_NAME_ALL_LIGHTS = 'all lights'
|
2018-03-02 01:14:26 +00:00
|
|
|
ENTITY_ID_ALL_LIGHTS = GROUP_ENTITY_ID_FORMAT.format('all_lights')
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2018-01-21 06:35:38 +00:00
|
|
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2016-08-16 06:07:07 +00:00
|
|
|
# Bitfield of features supported by the light entity
|
|
|
|
SUPPORT_BRIGHTNESS = 1
|
|
|
|
SUPPORT_COLOR_TEMP = 2
|
|
|
|
SUPPORT_EFFECT = 4
|
|
|
|
SUPPORT_FLASH = 8
|
|
|
|
SUPPORT_RGB_COLOR = 16
|
|
|
|
SUPPORT_TRANSITION = 32
|
|
|
|
SUPPORT_XY_COLOR = 64
|
2016-09-21 04:26:40 +00:00
|
|
|
SUPPORT_WHITE_VALUE = 128
|
2016-08-16 06:07:07 +00:00
|
|
|
|
2016-03-07 21:08:21 +00:00
|
|
|
# Integer that represents transition time in seconds to make change.
|
2014-03-16 22:00:59 +00:00
|
|
|
ATTR_TRANSITION = "transition"
|
|
|
|
|
2016-03-07 21:08:21 +00:00
|
|
|
# Lists holding color values
|
2014-03-16 22:00:59 +00:00
|
|
|
ATTR_RGB_COLOR = "rgb_color"
|
|
|
|
ATTR_XY_COLOR = "xy_color"
|
2015-10-28 23:12:16 +00:00
|
|
|
ATTR_COLOR_TEMP = "color_temp"
|
2017-05-17 06:00:46 +00:00
|
|
|
ATTR_KELVIN = "kelvin"
|
2017-04-29 22:04:20 +00:00
|
|
|
ATTR_MIN_MIREDS = "min_mireds"
|
|
|
|
ATTR_MAX_MIREDS = "max_mireds"
|
2016-05-17 07:06:55 +00:00
|
|
|
ATTR_COLOR_NAME = "color_name"
|
2016-09-21 04:26:40 +00:00
|
|
|
ATTR_WHITE_VALUE = "white_value"
|
2014-03-16 22:00:59 +00:00
|
|
|
|
2017-05-17 06:00:46 +00:00
|
|
|
# Brightness of the light, 0..255 or percentage
|
2014-03-16 22:00:59 +00:00
|
|
|
ATTR_BRIGHTNESS = "brightness"
|
2017-05-17 06:00:46 +00:00
|
|
|
ATTR_BRIGHTNESS_PCT = "brightness_pct"
|
2014-03-16 22:00:59 +00:00
|
|
|
|
2016-03-07 21:08:21 +00:00
|
|
|
# String representing a profile (built-in ones or external defined).
|
2014-03-26 07:08:50 +00:00
|
|
|
ATTR_PROFILE = "profile"
|
|
|
|
|
2016-03-07 21:08:21 +00:00
|
|
|
# If the light should flash, can be FLASH_SHORT or FLASH_LONG.
|
2014-12-09 07:02:38 +00:00
|
|
|
ATTR_FLASH = "flash"
|
|
|
|
FLASH_SHORT = "short"
|
|
|
|
FLASH_LONG = "long"
|
|
|
|
|
2016-11-28 01:15:28 +00:00
|
|
|
# List of possible effects
|
|
|
|
ATTR_EFFECT_LIST = "effect_list"
|
|
|
|
|
2016-03-07 21:08:21 +00:00
|
|
|
# Apply an effect to the light, can be EFFECT_COLORLOOP.
|
2015-07-08 18:26:37 +00:00
|
|
|
ATTR_EFFECT = "effect"
|
|
|
|
EFFECT_COLORLOOP = "colorloop"
|
2015-12-25 03:10:27 +00:00
|
|
|
EFFECT_RANDOM = "random"
|
2015-11-01 15:04:23 +00:00
|
|
|
EFFECT_WHITE = "white"
|
2015-07-08 18:26:37 +00:00
|
|
|
|
2017-06-02 06:05:05 +00:00
|
|
|
COLOR_GROUP = "Color descriptors"
|
|
|
|
|
2014-03-26 07:08:50 +00:00
|
|
|
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
|
|
|
|
2015-06-13 23:42:09 +00:00
|
|
|
PROP_TO_ATTR = {
|
|
|
|
'brightness': ATTR_BRIGHTNESS,
|
2015-10-28 23:12:16 +00:00
|
|
|
'color_temp': ATTR_COLOR_TEMP,
|
2017-04-29 22:04:20 +00:00
|
|
|
'min_mireds': ATTR_MIN_MIREDS,
|
|
|
|
'max_mireds': ATTR_MAX_MIREDS,
|
2015-11-07 09:25:33 +00:00
|
|
|
'rgb_color': ATTR_RGB_COLOR,
|
|
|
|
'xy_color': ATTR_XY_COLOR,
|
2016-09-21 04:26:40 +00:00
|
|
|
'white_value': ATTR_WHITE_VALUE,
|
2016-11-28 01:15:28 +00:00
|
|
|
'effect_list': ATTR_EFFECT_LIST,
|
|
|
|
'effect': ATTR_EFFECT,
|
2015-06-13 23:42:09 +00:00
|
|
|
}
|
|
|
|
|
2016-03-31 22:24:06 +00:00
|
|
|
# Service call validation schemas
|
2017-03-09 21:50:30 +00:00
|
|
|
VALID_TRANSITION = vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))
|
2016-07-11 19:39:46 +00:00
|
|
|
VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255))
|
2017-05-17 06:00:46 +00:00
|
|
|
VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100))
|
2016-03-31 22:24:06 +00:00
|
|
|
|
|
|
|
LIGHT_TURN_ON_SCHEMA = vol.Schema({
|
|
|
|
ATTR_ENTITY_ID: cv.entity_ids,
|
2017-06-02 06:05:05 +00:00
|
|
|
vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string,
|
2016-03-31 22:24:06 +00:00
|
|
|
ATTR_TRANSITION: VALID_TRANSITION,
|
2016-07-11 19:39:46 +00:00
|
|
|
ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
|
2017-05-17 06:00:46 +00:00
|
|
|
ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT,
|
2017-06-02 06:05:05 +00:00
|
|
|
vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string,
|
|
|
|
vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP):
|
|
|
|
vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)),
|
|
|
|
vol.Coerce(tuple)),
|
|
|
|
vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP):
|
|
|
|
vol.All(vol.ExactSequence((cv.small_float, cv.small_float)),
|
|
|
|
vol.Coerce(tuple)),
|
|
|
|
vol.Exclusive(ATTR_COLOR_TEMP, COLOR_GROUP):
|
|
|
|
vol.All(vol.Coerce(int), vol.Range(min=1)),
|
|
|
|
vol.Exclusive(ATTR_KELVIN, COLOR_GROUP):
|
|
|
|
vol.All(vol.Coerce(int), vol.Range(min=0)),
|
2016-12-02 02:26:53 +00:00
|
|
|
ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)),
|
2016-04-09 16:07:13 +00:00
|
|
|
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
|
2016-11-28 01:15:28 +00:00
|
|
|
ATTR_EFFECT: cv.string,
|
2016-03-31 22:24:06 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
LIGHT_TURN_OFF_SCHEMA = vol.Schema({
|
|
|
|
ATTR_ENTITY_ID: cv.entity_ids,
|
|
|
|
ATTR_TRANSITION: VALID_TRANSITION,
|
2016-09-04 10:04:12 +00:00
|
|
|
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
|
2016-03-31 22:24:06 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
LIGHT_TOGGLE_SCHEMA = vol.Schema({
|
|
|
|
ATTR_ENTITY_ID: cv.entity_ids,
|
|
|
|
ATTR_TRANSITION: VALID_TRANSITION,
|
|
|
|
})
|
|
|
|
|
2016-04-01 03:19:59 +00:00
|
|
|
PROFILE_SCHEMA = vol.Schema(
|
|
|
|
vol.ExactSequence((str, cv.small_float, cv.small_float, cv.byte))
|
|
|
|
)
|
|
|
|
|
2018-02-28 02:02:21 +00:00
|
|
|
INTENT_SET = 'HassLightSet'
|
|
|
|
|
2014-11-09 23:12:23 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2017-07-16 17:14:46 +00:00
|
|
|
@bind_hass
|
2014-04-24 07:40:45 +00:00
|
|
|
def is_on(hass, entity_id=None):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Return if the lights are on based on the statemachine."""
|
2014-01-21 07:23:02 +00:00
|
|
|
entity_id = entity_id or ENTITY_ID_ALL_LIGHTS
|
2014-04-24 07:40:45 +00:00
|
|
|
return hass.states.is_state(entity_id, STATE_ON)
|
2013-12-11 08:07:30 +00:00
|
|
|
|
|
|
|
|
2017-07-16 17:14:46 +00:00
|
|
|
@bind_hass
|
2014-04-24 07:40:45 +00:00
|
|
|
def turn_on(hass, entity_id=None, transition=None, brightness=None,
|
2017-05-17 06:00:46 +00:00
|
|
|
brightness_pct=None, rgb_color=None, xy_color=None,
|
|
|
|
color_temp=None, kelvin=None, white_value=None,
|
2016-09-21 04:26:40 +00:00
|
|
|
profile=None, flash=None, effect=None, color_name=None):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Turn all or specified light on."""
|
2017-02-23 20:57:25 +00:00
|
|
|
hass.add_job(
|
2017-05-17 06:00:46 +00:00
|
|
|
async_turn_on, hass, entity_id, transition, brightness, brightness_pct,
|
|
|
|
rgb_color, xy_color, color_temp, kelvin, white_value,
|
2017-02-23 20:57:25 +00:00
|
|
|
profile, flash, effect, color_name)
|
2016-11-05 23:36:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
2017-07-16 16:23:06 +00:00
|
|
|
@bind_hass
|
2016-11-05 23:36:20 +00:00
|
|
|
def async_turn_on(hass, entity_id=None, transition=None, brightness=None,
|
2017-05-17 06:00:46 +00:00
|
|
|
brightness_pct=None, rgb_color=None, xy_color=None,
|
|
|
|
color_temp=None, kelvin=None, white_value=None,
|
|
|
|
profile=None, flash=None, effect=None, color_name=None):
|
2016-11-05 23:36:20 +00:00
|
|
|
"""Turn all or specified light on."""
|
2014-12-09 07:02:38 +00:00
|
|
|
data = {
|
|
|
|
key: value for key, value in [
|
|
|
|
(ATTR_ENTITY_ID, entity_id),
|
|
|
|
(ATTR_PROFILE, profile),
|
|
|
|
(ATTR_TRANSITION, transition),
|
|
|
|
(ATTR_BRIGHTNESS, brightness),
|
2017-05-17 06:00:46 +00:00
|
|
|
(ATTR_BRIGHTNESS_PCT, brightness_pct),
|
2014-12-09 07:02:38 +00:00
|
|
|
(ATTR_RGB_COLOR, rgb_color),
|
|
|
|
(ATTR_XY_COLOR, xy_color),
|
2015-10-28 23:12:16 +00:00
|
|
|
(ATTR_COLOR_TEMP, color_temp),
|
2017-05-17 06:00:46 +00:00
|
|
|
(ATTR_KELVIN, kelvin),
|
2016-09-21 04:26:40 +00:00
|
|
|
(ATTR_WHITE_VALUE, white_value),
|
2014-12-09 07:02:38 +00:00
|
|
|
(ATTR_FLASH, flash),
|
2015-07-08 18:26:37 +00:00
|
|
|
(ATTR_EFFECT, effect),
|
2016-05-17 07:06:55 +00:00
|
|
|
(ATTR_COLOR_NAME, color_name),
|
2014-12-09 07:02:38 +00:00
|
|
|
] if value is not None
|
|
|
|
}
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2016-11-30 21:33:38 +00:00
|
|
|
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
|
2013-12-11 08:07:30 +00:00
|
|
|
|
|
|
|
|
2017-07-16 17:14:46 +00:00
|
|
|
@bind_hass
|
2014-04-24 07:40:45 +00:00
|
|
|
def turn_off(hass, entity_id=None, transition=None):
|
2016-11-05 23:36:20 +00:00
|
|
|
"""Turn all or specified light off."""
|
2017-02-23 20:57:25 +00:00
|
|
|
hass.add_job(async_turn_off, hass, entity_id, transition)
|
2016-11-05 23:36:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
2017-07-16 17:14:46 +00:00
|
|
|
@bind_hass
|
2016-11-05 23:36:20 +00:00
|
|
|
def async_turn_off(hass, entity_id=None, transition=None):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Turn all or specified light off."""
|
2014-12-09 07:02:38 +00:00
|
|
|
data = {
|
|
|
|
key: value for key, value in [
|
|
|
|
(ATTR_ENTITY_ID, entity_id),
|
|
|
|
(ATTR_TRANSITION, transition),
|
|
|
|
] if value is not None
|
|
|
|
}
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2016-11-30 21:33:38 +00:00
|
|
|
hass.async_add_job(hass.services.async_call(
|
|
|
|
DOMAIN, SERVICE_TURN_OFF, data))
|
2013-12-11 08:07:30 +00:00
|
|
|
|
|
|
|
|
2018-03-02 01:14:26 +00:00
|
|
|
@callback
|
2017-07-16 17:14:46 +00:00
|
|
|
@bind_hass
|
2018-03-02 01:14:26 +00:00
|
|
|
def async_toggle(hass, entity_id=None, transition=None):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Toggle all or specified light."""
|
2016-01-16 15:45:05 +00:00
|
|
|
data = {
|
|
|
|
key: value for key, value in [
|
|
|
|
(ATTR_ENTITY_ID, entity_id),
|
|
|
|
(ATTR_TRANSITION, transition),
|
|
|
|
] if value is not None
|
|
|
|
}
|
|
|
|
|
2018-03-02 01:14:26 +00:00
|
|
|
hass.async_add_job(hass.services.async_call(
|
|
|
|
DOMAIN, SERVICE_TOGGLE, data))
|
|
|
|
|
|
|
|
|
|
|
|
@bind_hass
|
|
|
|
def toggle(hass, entity_id=None, transition=None):
|
|
|
|
"""Toggle all or specified light."""
|
|
|
|
hass.add_job(async_toggle, hass, entity_id, transition)
|
2016-01-16 15:45:05 +00:00
|
|
|
|
|
|
|
|
2017-05-17 06:00:46 +00:00
|
|
|
def preprocess_turn_on_alternatives(params):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Process extra data for turn light on request."""
|
2017-05-17 06:00:46 +00:00
|
|
|
profile = Profiles.get(params.pop(ATTR_PROFILE, None))
|
|
|
|
if profile is not None:
|
|
|
|
params.setdefault(ATTR_XY_COLOR, profile[:2])
|
|
|
|
params.setdefault(ATTR_BRIGHTNESS, profile[2])
|
|
|
|
|
|
|
|
color_name = params.pop(ATTR_COLOR_NAME, None)
|
|
|
|
if color_name is not None:
|
2018-02-28 02:02:21 +00:00
|
|
|
try:
|
|
|
|
params[ATTR_RGB_COLOR] = color_util.color_name_to_rgb(color_name)
|
|
|
|
except ValueError:
|
|
|
|
_LOGGER.warning('Got unknown color %s, falling back to white',
|
|
|
|
color_name)
|
|
|
|
params[ATTR_RGB_COLOR] = (255, 255, 255)
|
2017-05-17 06:00:46 +00:00
|
|
|
|
|
|
|
kelvin = params.pop(ATTR_KELVIN, None)
|
|
|
|
if kelvin is not None:
|
|
|
|
mired = color_util.color_temperature_kelvin_to_mired(kelvin)
|
2017-05-18 02:20:59 +00:00
|
|
|
params[ATTR_COLOR_TEMP] = int(mired)
|
2017-05-17 06:00:46 +00:00
|
|
|
|
|
|
|
brightness_pct = params.pop(ATTR_BRIGHTNESS_PCT, None)
|
|
|
|
if brightness_pct is not None:
|
|
|
|
params[ATTR_BRIGHTNESS] = int(255 * brightness_pct/100)
|
|
|
|
|
|
|
|
|
2018-02-28 02:02:21 +00:00
|
|
|
class SetIntentHandler(intent.IntentHandler):
|
|
|
|
"""Handle set color intents."""
|
|
|
|
|
|
|
|
intent_type = INTENT_SET
|
|
|
|
slot_schema = {
|
|
|
|
vol.Required('name'): cv.string,
|
|
|
|
vol.Optional('color'): color_util.color_name_to_rgb,
|
|
|
|
vol.Optional('brightness'): vol.All(vol.Coerce(int), vol.Range(0, 100))
|
|
|
|
}
|
|
|
|
|
|
|
|
async def async_handle(self, intent_obj):
|
|
|
|
"""Handle the hass intent."""
|
|
|
|
hass = intent_obj.hass
|
|
|
|
slots = self.async_validate_slots(intent_obj.slots)
|
|
|
|
state = hass.helpers.intent.async_match_state(
|
|
|
|
slots['name']['value'],
|
|
|
|
[state for state in hass.states.async_all()
|
|
|
|
if state.domain == DOMAIN])
|
|
|
|
|
|
|
|
service_data = {
|
|
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
|
|
}
|
|
|
|
speech_parts = []
|
|
|
|
|
|
|
|
if 'color' in slots:
|
|
|
|
intent.async_test_feature(
|
|
|
|
state, SUPPORT_RGB_COLOR, 'changing colors')
|
|
|
|
service_data[ATTR_RGB_COLOR] = slots['color']['value']
|
|
|
|
# Use original passed in value of the color because we don't have
|
|
|
|
# human readable names for that internally.
|
|
|
|
speech_parts.append('the color {}'.format(
|
|
|
|
intent_obj.slots['color']['value']))
|
|
|
|
|
|
|
|
if 'brightness' in slots:
|
|
|
|
intent.async_test_feature(
|
|
|
|
state, SUPPORT_BRIGHTNESS, 'changing brightness')
|
|
|
|
service_data[ATTR_BRIGHTNESS_PCT] = slots['brightness']['value']
|
|
|
|
speech_parts.append('{}% brightness'.format(
|
|
|
|
slots['brightness']['value']))
|
|
|
|
|
|
|
|
await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, service_data)
|
|
|
|
|
|
|
|
response = intent_obj.create_response()
|
|
|
|
|
|
|
|
if not speech_parts: # No attributes changed
|
|
|
|
speech = 'Turned on {}'.format(state.name)
|
|
|
|
else:
|
|
|
|
parts = ['Changed {} to'.format(state.name)]
|
|
|
|
for index, part in enumerate(speech_parts):
|
|
|
|
if index == 0:
|
|
|
|
parts.append(' {}'.format(part))
|
|
|
|
elif index != len(speech_parts) - 1:
|
|
|
|
parts.append(', {}'.format(part))
|
|
|
|
else:
|
|
|
|
parts.append(' and {}'.format(part))
|
|
|
|
speech = ''.join(parts)
|
|
|
|
|
|
|
|
response.async_set_speech(speech)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
async def async_setup(hass, config):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Expose light control via state machine and services."""
|
2015-03-22 02:37:18 +00:00
|
|
|
component = EntityComponent(
|
2016-06-12 00:43:13 +00:00
|
|
|
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LIGHTS)
|
2018-02-24 18:24:33 +00:00
|
|
|
await component.async_setup(config)
|
2015-03-01 09:35:58 +00:00
|
|
|
|
2016-11-30 21:33:38 +00:00
|
|
|
# load profiles from files
|
2018-02-24 18:24:33 +00:00
|
|
|
profiles_valid = await Profiles.load_profiles(hass)
|
2017-05-17 06:00:46 +00:00
|
|
|
if not profiles_valid:
|
2016-11-30 21:33:38 +00:00
|
|
|
return False
|
2015-08-03 15:42:28 +00:00
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
async def async_handle_light_service(service):
|
2018-01-29 22:37:19 +00:00
|
|
|
"""Handle a turn light on or off service call."""
|
2016-03-31 22:24:06 +00:00
|
|
|
# Get the validated data
|
|
|
|
params = service.data.copy()
|
2014-03-16 22:00:59 +00:00
|
|
|
|
2014-03-25 03:34:35 +00:00
|
|
|
# Convert the entity ids to valid light ids
|
2016-11-30 21:33:38 +00:00
|
|
|
target_lights = component.async_extract_from_service(service)
|
2016-03-31 22:24:06 +00:00
|
|
|
params.pop(ATTR_ENTITY_ID, None)
|
2014-11-26 05:28:43 +00:00
|
|
|
|
2017-05-17 06:00:46 +00:00
|
|
|
preprocess_turn_on_alternatives(params)
|
2016-05-17 07:06:55 +00:00
|
|
|
|
2017-10-19 08:56:25 +00:00
|
|
|
update_tasks = []
|
2015-08-03 15:42:28 +00:00
|
|
|
for light in target_lights:
|
2016-11-30 21:33:38 +00:00
|
|
|
if service.service == SERVICE_TURN_ON:
|
2018-02-24 18:24:33 +00:00
|
|
|
await light.async_turn_on(**params)
|
2016-11-30 21:33:38 +00:00
|
|
|
elif service.service == SERVICE_TURN_OFF:
|
2018-02-24 18:24:33 +00:00
|
|
|
await light.async_turn_off(**params)
|
2016-11-30 21:33:38 +00:00
|
|
|
else:
|
2018-02-24 18:24:33 +00:00
|
|
|
await light.async_toggle(**params)
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2016-12-07 06:30:47 +00:00
|
|
|
if not light.should_poll:
|
|
|
|
continue
|
2017-10-19 08:56:25 +00:00
|
|
|
update_tasks.append(light.async_update_ha_state(True))
|
2016-11-30 21:33:38 +00:00
|
|
|
|
|
|
|
if update_tasks:
|
2018-02-24 18:24:33 +00:00
|
|
|
await asyncio.wait(update_tasks, loop=hass.loop)
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2016-03-07 21:08:21 +00:00
|
|
|
# Listen for light on and light off service calls.
|
2016-11-30 21:33:38 +00:00
|
|
|
hass.services.async_register(
|
|
|
|
DOMAIN, SERVICE_TURN_ON, async_handle_light_service,
|
2018-01-07 22:54:16 +00:00
|
|
|
schema=LIGHT_TURN_ON_SCHEMA)
|
2013-12-11 08:07:30 +00:00
|
|
|
|
2016-11-30 21:33:38 +00:00
|
|
|
hass.services.async_register(
|
|
|
|
DOMAIN, SERVICE_TURN_OFF, async_handle_light_service,
|
2018-01-07 22:54:16 +00:00
|
|
|
schema=LIGHT_TURN_OFF_SCHEMA)
|
2016-11-30 21:33:38 +00:00
|
|
|
|
|
|
|
hass.services.async_register(
|
|
|
|
DOMAIN, SERVICE_TOGGLE, async_handle_light_service,
|
2018-01-07 22:54:16 +00:00
|
|
|
schema=LIGHT_TOGGLE_SCHEMA)
|
2016-01-16 15:45:05 +00:00
|
|
|
|
2018-02-28 02:02:21 +00:00
|
|
|
hass.helpers.intent.async_register(SetIntentHandler())
|
|
|
|
|
2013-12-11 08:07:30 +00:00
|
|
|
return True
|
2015-06-13 23:42:09 +00:00
|
|
|
|
|
|
|
|
2017-05-17 06:00:46 +00:00
|
|
|
class Profiles:
|
|
|
|
"""Representation of available color profiles."""
|
|
|
|
|
|
|
|
_all = None
|
|
|
|
|
|
|
|
@classmethod
|
2018-02-24 18:24:33 +00:00
|
|
|
async def load_profiles(cls, hass):
|
2017-05-17 06:00:46 +00:00
|
|
|
"""Load and cache profiles."""
|
|
|
|
def load_profile_data(hass):
|
|
|
|
"""Load built-in profiles and custom profiles."""
|
|
|
|
profile_paths = [os.path.join(os.path.dirname(__file__),
|
|
|
|
LIGHT_PROFILES_FILE),
|
|
|
|
hass.config.path(LIGHT_PROFILES_FILE)]
|
|
|
|
profiles = {}
|
|
|
|
|
|
|
|
for profile_path in profile_paths:
|
|
|
|
if not os.path.isfile(profile_path):
|
|
|
|
continue
|
|
|
|
with open(profile_path) as inp:
|
|
|
|
reader = csv.reader(inp)
|
|
|
|
|
|
|
|
# Skip the header
|
|
|
|
next(reader, None)
|
|
|
|
|
|
|
|
try:
|
|
|
|
for rec in reader:
|
|
|
|
profile, color_x, color_y, brightness = \
|
|
|
|
PROFILE_SCHEMA(rec)
|
|
|
|
profiles[profile] = (color_x, color_y, brightness)
|
|
|
|
except vol.MultipleInvalid as ex:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Error parsing light profile from %s: %s",
|
|
|
|
profile_path, ex)
|
|
|
|
return None
|
|
|
|
return profiles
|
|
|
|
|
2018-02-24 18:24:33 +00:00
|
|
|
cls._all = await hass.async_add_job(load_profile_data, hass)
|
2017-05-17 06:00:46 +00:00
|
|
|
return cls._all is not None
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get(cls, name):
|
|
|
|
"""Return a named profile."""
|
|
|
|
return cls._all.get(name)
|
2016-11-30 21:33:38 +00:00
|
|
|
|
|
|
|
|
2015-06-13 23:42:09 +00:00
|
|
|
class Light(ToggleEntity):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Representation of a light."""
|
2015-06-13 23:42:09 +00:00
|
|
|
|
2016-11-04 01:32:14 +00:00
|
|
|
# pylint: disable=no-self-use
|
2016-06-25 04:27:40 +00:00
|
|
|
|
2015-06-13 23:42:09 +00:00
|
|
|
@property
|
|
|
|
def brightness(self):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Return the brightness of this light between 0..255."""
|
2015-06-13 23:42:09 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
2015-11-07 09:25:33 +00:00
|
|
|
def xy_color(self):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Return the XY color value [float, float]."""
|
2015-06-13 23:42:09 +00:00
|
|
|
return None
|
|
|
|
|
2015-11-07 09:25:33 +00:00
|
|
|
@property
|
|
|
|
def rgb_color(self):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Return the RGB color value [int, int, int]."""
|
2015-11-07 09:25:33 +00:00
|
|
|
return None
|
|
|
|
|
2015-10-27 22:34:49 +00:00
|
|
|
@property
|
2015-10-28 23:12:16 +00:00
|
|
|
def color_temp(self):
|
2016-03-20 02:44:20 +00:00
|
|
|
"""Return the CT color value in mireds."""
|
2015-10-27 22:34:49 +00:00
|
|
|
return None
|
|
|
|
|
2017-04-29 22:04:20 +00:00
|
|
|
@property
|
|
|
|
def min_mireds(self):
|
|
|
|
"""Return the coldest color_temp that this light supports."""
|
|
|
|
# Default to the Philips Hue value that HA has always assumed
|
|
|
|
return 154
|
|
|
|
|
|
|
|
@property
|
|
|
|
def max_mireds(self):
|
|
|
|
"""Return the warmest color_temp that this light supports."""
|
|
|
|
# Default to the Philips Hue value that HA has always assumed
|
|
|
|
return 500
|
|
|
|
|
2016-09-21 04:26:40 +00:00
|
|
|
@property
|
|
|
|
def white_value(self):
|
|
|
|
"""Return the white value of this light between 0..255."""
|
|
|
|
return None
|
|
|
|
|
2016-11-28 01:15:28 +00:00
|
|
|
@property
|
|
|
|
def effect_list(self):
|
|
|
|
"""Return the list of supported effects."""
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def effect(self):
|
|
|
|
"""Return the current effect."""
|
|
|
|
return None
|
|
|
|
|
2015-06-13 23:42:09 +00:00
|
|
|
@property
|
|
|
|
def state_attributes(self):
|
2016-03-07 21:08:21 +00:00
|
|
|
"""Return optional state attributes."""
|
2015-06-13 23:42:09 +00:00
|
|
|
data = {}
|
|
|
|
|
|
|
|
if self.is_on:
|
|
|
|
for prop, attr in PROP_TO_ATTR.items():
|
|
|
|
value = getattr(self, prop)
|
2016-08-16 06:07:07 +00:00
|
|
|
if value is not None:
|
2015-06-13 23:42:09 +00:00
|
|
|
data[attr] = value
|
|
|
|
|
2015-11-07 09:25:33 +00:00
|
|
|
if ATTR_RGB_COLOR not in data and ATTR_XY_COLOR in data and \
|
|
|
|
ATTR_BRIGHTNESS in data:
|
|
|
|
data[ATTR_RGB_COLOR] = color_util.color_xy_brightness_to_RGB(
|
|
|
|
data[ATTR_XY_COLOR][0], data[ATTR_XY_COLOR][1],
|
|
|
|
data[ATTR_BRIGHTNESS])
|
|
|
|
|
2015-06-13 23:42:09 +00:00
|
|
|
return data
|
2016-08-16 06:07:07 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag supported features."""
|
|
|
|
return 0
|