Lights now support profiles
This commit is contained in:
parent
2890f2d6cc
commit
90769fc0eb
6 changed files with 132 additions and 34 deletions
|
@ -29,6 +29,8 @@ download_dir=downloads
|
||||||
[device_sun_light_trigger]
|
[device_sun_light_trigger]
|
||||||
# Example how you can specify a specific group that has to be turned on
|
# Example how you can specify a specific group that has to be turned on
|
||||||
# light_group=group.living_room
|
# light_group=group.living_room
|
||||||
|
# Example how you can specify which light profile to use when turning lights on
|
||||||
|
# light_profile=relax
|
||||||
|
|
||||||
# A comma seperated list of states that have to be tracked
|
# A comma seperated list of states that have to be tracked
|
||||||
# As a single group
|
# As a single group
|
||||||
|
|
|
@ -184,10 +184,12 @@ def from_config_file(config_path):
|
||||||
device_sun_light_trigger = load_module('device_sun_light_trigger')
|
device_sun_light_trigger = load_module('device_sun_light_trigger')
|
||||||
|
|
||||||
light_group = get_opt_safe("device_sun_light_trigger", "light_group")
|
light_group = get_opt_safe("device_sun_light_trigger", "light_group")
|
||||||
|
light_profile = get_opt_safe("device_sun_light_trigger",
|
||||||
|
"light_profile")
|
||||||
|
|
||||||
add_status("Device Sun Light Trigger",
|
add_status("Device Sun Light Trigger",
|
||||||
device_sun_light_trigger.setup(bus, statemachine,
|
device_sun_light_trigger.setup(bus, statemachine,
|
||||||
light_group))
|
light_group, light_profile))
|
||||||
|
|
||||||
for component, success_init in statusses:
|
for component, success_init in statusses:
|
||||||
status = "initialized" if success_init else "Failed to initialize"
|
status = "initialized" if success_init else "Failed to initialize"
|
||||||
|
|
|
@ -15,12 +15,15 @@ from . import light, sun, device_tracker, group
|
||||||
|
|
||||||
|
|
||||||
LIGHT_TRANSITION_TIME = timedelta(minutes=15)
|
LIGHT_TRANSITION_TIME = timedelta(minutes=15)
|
||||||
LIGHT_BRIGHTNESS = 164
|
|
||||||
LIGHT_XY_COLOR = [0.5119, 0.4147]
|
# Light profile to be used if none given
|
||||||
|
LIGHT_PROFILE = 'relax'
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
def setup(bus, statemachine, light_group=None):
|
def setup(bus, statemachine,
|
||||||
|
light_group=light.GROUP_NAME_ALL_LIGHTS,
|
||||||
|
light_profile=LIGHT_PROFILE):
|
||||||
""" Triggers to turn lights on or off based on device precense. """
|
""" Triggers to turn lights on or off based on device precense. """
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -29,18 +32,16 @@ def setup(bus, statemachine, light_group=None):
|
||||||
device_tracker.DOMAIN)
|
device_tracker.DOMAIN)
|
||||||
|
|
||||||
if not device_entity_ids:
|
if not device_entity_ids:
|
||||||
logger.error("LightTrigger:No devices found to track")
|
logger.error("No devices found to track")
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
light_group = light_group or light.GROUP_NAME_ALL_LIGHTS
|
|
||||||
|
|
||||||
# Get the light IDs from the specified group
|
# Get the light IDs from the specified group
|
||||||
light_ids = util.filter_entity_ids(
|
light_ids = util.filter_entity_ids(
|
||||||
group.get_entity_ids(statemachine, light_group), light.DOMAIN)
|
group.get_entity_ids(statemachine, light_group), light.DOMAIN)
|
||||||
|
|
||||||
if not light_ids:
|
if not light_ids:
|
||||||
logger.error("LightTrigger:No lights found to turn on ")
|
logger.error("No lights found to turn on ")
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -68,8 +69,7 @@ def setup(bus, statemachine, light_group=None):
|
||||||
|
|
||||||
light.turn_on(bus, light_id,
|
light.turn_on(bus, light_id,
|
||||||
transition=LIGHT_TRANSITION_TIME.seconds,
|
transition=LIGHT_TRANSITION_TIME.seconds,
|
||||||
brightness=LIGHT_BRIGHTNESS,
|
profile=light_profile)
|
||||||
xy_color=LIGHT_XY_COLOR)
|
|
||||||
|
|
||||||
def turn_on(light_id):
|
def turn_on(light_id):
|
||||||
""" Lambda can keep track of function parameters but not local
|
""" Lambda can keep track of function parameters but not local
|
||||||
|
@ -121,8 +121,7 @@ def setup(bus, statemachine, light_group=None):
|
||||||
# So we skip fetching the entity ids again.
|
# So we skip fetching the entity ids again.
|
||||||
for light_id in light_ids:
|
for light_id in light_ids:
|
||||||
light.turn_on(bus, light_id,
|
light.turn_on(bus, light_id,
|
||||||
brightness=LIGHT_BRIGHTNESS,
|
profile=light_profile)
|
||||||
xy_color=LIGHT_XY_COLOR)
|
|
||||||
|
|
||||||
# Are we in the time span were we would turn on the lights
|
# Are we in the time span were we would turn on the lights
|
||||||
# if someone would be home?
|
# if someone would be home?
|
||||||
|
|
|
@ -3,12 +3,57 @@ homeassistant.components.light
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Provides functionality to interact with lights.
|
Provides functionality to interact with lights.
|
||||||
|
|
||||||
|
It offers the following services:
|
||||||
|
|
||||||
|
TURN_OFF - Turns one or multiple lights off.
|
||||||
|
|
||||||
|
Supports following parameters:
|
||||||
|
- transition
|
||||||
|
Integer that represents the time the light should take to transition to
|
||||||
|
the new state.
|
||||||
|
- entity_id
|
||||||
|
String or list of strings that point at entity_ids of lights.
|
||||||
|
|
||||||
|
TURN_ON - Turns one or multiple lights on and change attributes.
|
||||||
|
|
||||||
|
Supports following parameters:
|
||||||
|
- transition
|
||||||
|
Integer that represents the time the light should take to transition to
|
||||||
|
the new state.
|
||||||
|
|
||||||
|
- entity_id
|
||||||
|
String or list of strings that point at entity_ids of lights.
|
||||||
|
|
||||||
|
- profile
|
||||||
|
String with the name of one of the built-in profiles (relax, energize,
|
||||||
|
concentrate, reading) or one of the custom profiles defined in
|
||||||
|
light_profiles.csv in the current working directory.
|
||||||
|
|
||||||
|
Light profiles define a xy color and a brightness.
|
||||||
|
|
||||||
|
If a profile is given and a brightness or xy color then the profile values
|
||||||
|
will be overwritten.
|
||||||
|
|
||||||
|
- xy_color
|
||||||
|
A list containing two floats representing the xy color you want the light
|
||||||
|
to be.
|
||||||
|
|
||||||
|
- rgb_color
|
||||||
|
A list containing three integers representing the xy color you want the
|
||||||
|
light to be.
|
||||||
|
|
||||||
|
- brightness
|
||||||
|
Integer between 0 and 255 representing how bright you want the light to be.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
|
||||||
import homeassistant as ha
|
import homeassistant as ha
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
@ -38,6 +83,11 @@ ATTR_XY_COLOR = "xy_color"
|
||||||
# int with value 0 .. 255 representing brightness of the light
|
# int with value 0 .. 255 representing brightness of the light
|
||||||
ATTR_BRIGHTNESS = "brightness"
|
ATTR_BRIGHTNESS = "brightness"
|
||||||
|
|
||||||
|
# String representing a profile (built-in ones or external defined)
|
||||||
|
ATTR_PROFILE = "profile"
|
||||||
|
|
||||||
|
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
||||||
|
|
||||||
|
|
||||||
def is_on(statemachine, entity_id=None):
|
def is_on(statemachine, entity_id=None):
|
||||||
""" Returns if the lights are on based on the statemachine. """
|
""" Returns if the lights are on based on the statemachine. """
|
||||||
|
@ -48,23 +98,26 @@ def is_on(statemachine, entity_id=None):
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def turn_on(bus, entity_id=None, transition=None, brightness=None,
|
def turn_on(bus, entity_id=None, transition=None, brightness=None,
|
||||||
rgb_color=None, xy_color=None):
|
rgb_color=None, xy_color=None, profile=None):
|
||||||
""" Turns all or specified light on. """
|
""" Turns all or specified light on. """
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
if entity_id:
|
if entity_id:
|
||||||
data[ATTR_ENTITY_ID] = entity_id
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
if profile:
|
||||||
|
data[ATTR_PROFILE] = profile
|
||||||
|
|
||||||
if transition is not None:
|
if transition is not None:
|
||||||
data[ATTR_TRANSITION] = transition
|
data[ATTR_TRANSITION] = transition
|
||||||
|
|
||||||
if brightness is not None:
|
if brightness is not None:
|
||||||
data[ATTR_BRIGHTNESS] = brightness
|
data[ATTR_BRIGHTNESS] = brightness
|
||||||
|
|
||||||
if rgb_color is not None:
|
if rgb_color:
|
||||||
data[ATTR_RGB_COLOR] = rgb_color
|
data[ATTR_RGB_COLOR] = rgb_color
|
||||||
|
|
||||||
if xy_color is not None:
|
if xy_color:
|
||||||
data[ATTR_XY_COLOR] = xy_color
|
data[ATTR_XY_COLOR] = xy_color
|
||||||
|
|
||||||
bus.call_service(DOMAIN, SERVICE_TURN_ON, data)
|
bus.call_service(DOMAIN, SERVICE_TURN_ON, data)
|
||||||
|
@ -83,7 +136,7 @@ def turn_off(bus, entity_id=None, transition=None):
|
||||||
bus.call_service(DOMAIN, SERVICE_TURN_OFF, data)
|
bus.call_service(DOMAIN, SERVICE_TURN_OFF, data)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches, too-many-locals
|
||||||
def setup(bus, statemachine, light_control):
|
def setup(bus, statemachine, light_control):
|
||||||
""" Exposes light control via statemachine and services. """
|
""" Exposes light control via statemachine and services. """
|
||||||
|
|
||||||
|
@ -149,10 +202,42 @@ def setup(bus, statemachine, light_control):
|
||||||
# Update light state and discover lights for tracking the group
|
# Update light state and discover lights for tracking the group
|
||||||
update_lights_state(None, True)
|
update_lights_state(None, True)
|
||||||
|
|
||||||
|
if len(ent_to_light) == 0:
|
||||||
|
logger.error("No lights found")
|
||||||
|
return False
|
||||||
|
|
||||||
# Track all lights in a group
|
# Track all lights in a group
|
||||||
group.setup(bus, statemachine,
|
group.setup(bus, statemachine,
|
||||||
GROUP_NAME_ALL_LIGHTS, light_to_ent.values())
|
GROUP_NAME_ALL_LIGHTS, light_to_ent.values())
|
||||||
|
|
||||||
|
# Load built-in profiles and custom profiles
|
||||||
|
profile_paths = [os.path.dirname(__file__), os.getcwd()]
|
||||||
|
profiles = {}
|
||||||
|
|
||||||
|
for dir_path in profile_paths:
|
||||||
|
file_path = os.path.join(dir_path, LIGHT_PROFILES_FILE)
|
||||||
|
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
with open(file_path, 'rb') as inp:
|
||||||
|
reader = csv.reader(inp)
|
||||||
|
|
||||||
|
# Skip the header
|
||||||
|
next(reader, None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for profile_id, color_x, color_y, brightness in reader:
|
||||||
|
profiles[profile_id] = (float(color_x), float(color_y),
|
||||||
|
int(brightness))
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
# ValueError if not 4 values per row
|
||||||
|
# ValueError if convert to float/int failed
|
||||||
|
logger.error(
|
||||||
|
"Error parsing light profiles from {}".format(
|
||||||
|
file_path))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def handle_light_service(service):
|
def handle_light_service(service):
|
||||||
""" Hande a turn light on or off service call. """
|
""" Hande a turn light on or off service call. """
|
||||||
# Get and validate data
|
# Get and validate data
|
||||||
|
@ -166,36 +251,46 @@ def setup(bus, statemachine, light_control):
|
||||||
if not light_ids:
|
if not light_ids:
|
||||||
light_ids = ent_to_light.values()
|
light_ids = ent_to_light.values()
|
||||||
|
|
||||||
transition = util.dict_get_convert(dat, ATTR_TRANSITION, int, None)
|
transition = util.convert(dat.get(ATTR_TRANSITION), int)
|
||||||
|
|
||||||
if service.service == SERVICE_TURN_OFF:
|
if service.service == SERVICE_TURN_OFF:
|
||||||
light_control.turn_light_off(light_ids, transition)
|
light_control.turn_light_off(light_ids, transition)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Processing extra data for turn light on request
|
# Processing extra data for turn light on request
|
||||||
bright = util.dict_get_convert(dat, ATTR_BRIGHTNESS, int, 164)
|
|
||||||
|
|
||||||
color = None
|
# We process the profile first so that we get the desired
|
||||||
xy_color = dat.get(ATTR_XY_COLOR)
|
# behavior that extra service data attributes overwrite
|
||||||
rgb_color = dat.get(ATTR_RGB_COLOR)
|
# profile values
|
||||||
|
profile = profiles.get(dat.get(ATTR_PROFILE))
|
||||||
|
|
||||||
if xy_color:
|
if profile:
|
||||||
|
color = profile[0:2]
|
||||||
|
bright = profile[2]
|
||||||
|
else:
|
||||||
|
color = None
|
||||||
|
bright = None
|
||||||
|
|
||||||
|
if ATTR_BRIGHTNESS in dat:
|
||||||
|
bright = util.convert(dat.get(ATTR_BRIGHTNESS), int)
|
||||||
|
|
||||||
|
if ATTR_XY_COLOR in dat:
|
||||||
try:
|
try:
|
||||||
# xy_color should be a list containing 2 floats
|
# xy_color should be a list containing 2 floats
|
||||||
xy_color = [float(val) for val in xy_color]
|
xy_color = [float(val) for val in dat.get(ATTR_XY_COLOR)]
|
||||||
|
|
||||||
if len(xy_color) == 2:
|
if len(xy_color) == 2:
|
||||||
color = xy_color
|
color = xy_color
|
||||||
|
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
# TypeError if xy_color was not iterable
|
# TypeError if dat[ATTR_XY_COLOR] is not iterable
|
||||||
# ValueError if value could not be converted to float
|
# ValueError if value could not be converted to float
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not color and rgb_color:
|
if ATTR_RGB_COLOR in dat:
|
||||||
try:
|
try:
|
||||||
# rgb_color should be a list containing 3 ints
|
# rgb_color should be a list containing 3 ints
|
||||||
rgb_color = [int(val) for val in rgb_color]
|
rgb_color = [int(val) for val in dat.get(ATTR_RGB_COLOR)]
|
||||||
|
|
||||||
if len(rgb_color) == 3:
|
if len(rgb_color) == 3:
|
||||||
color = util.color_RGB_to_xy(rgb_color[0],
|
color = util.color_RGB_to_xy(rgb_color[0],
|
||||||
|
@ -203,8 +298,8 @@ def setup(bus, statemachine, light_control):
|
||||||
rgb_color[2])
|
rgb_color[2])
|
||||||
|
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
# TypeError if color has no len
|
# TypeError if dat[ATTR_RGB_COLOR] is not iterable
|
||||||
# ValueError if not all values convertable to int
|
# ValueError if not all values can be converted to int
|
||||||
pass
|
pass
|
||||||
|
|
||||||
light_control.turn_light_on(light_ids, transition, bright, color)
|
light_control.turn_light_on(light_ids, transition, bright, color)
|
5
homeassistant/components/light/light_profiles.csv
Normal file
5
homeassistant/components/light/light_profiles.csv
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
id,x,y,brightness
|
||||||
|
relax,0.5119,0.4147,144
|
||||||
|
concentrate,0.5119,0.4147,219
|
||||||
|
energize,0.368,0.3686,203
|
||||||
|
reading,0.4448,0.4066,240
|
|
|
@ -105,15 +105,10 @@ def color_RGB_to_xy(R, G, B):
|
||||||
return X / (X + Y + Z), Y / (X + Y + Z)
|
return X / (X + Y + Z), Y / (X + Y + Z)
|
||||||
|
|
||||||
|
|
||||||
def dict_get_convert(dic, key, value_type, default=None):
|
|
||||||
""" Get a value from a dic and ensure it is value_type. """
|
|
||||||
return convert(dic[key], value_type, default) if key in dic else default
|
|
||||||
|
|
||||||
|
|
||||||
def convert(value, to_type, default=None):
|
def convert(value, to_type, default=None):
|
||||||
""" Converts value to to_type, returns default if fails. """
|
""" Converts value to to_type, returns default if fails. """
|
||||||
try:
|
try:
|
||||||
return to_type(value)
|
return default if value is None else to_type(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# If value could not be converted
|
# If value could not be converted
|
||||||
return default
|
return default
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue