Reworked lights under the hood, better decoupling and less API calls

This commit is contained in:
Paulus Schoutsen 2014-03-15 00:24:28 -07:00
parent 7cc9034815
commit 3c79a5ce0a

View File

@ -6,12 +6,15 @@ Provides functionality to interact with lights.
""" """
import logging import logging
import socket
from datetime import datetime, timedelta from datetime import datetime, timedelta
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
import homeassistant.components as components from homeassistant.components import (group, STATE_ON, STATE_OFF,
from homeassistant.components import group SERVICE_TURN_ON, SERVICE_TURN_OFF,
ATTR_ENTITY_ID)
DOMAIN = "light" DOMAIN = "light"
@ -28,35 +31,33 @@ 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. """
entity_id = entity_id or ENTITY_ID_ALL_LIGHTS entity_id = entity_id or ENTITY_ID_ALL_LIGHTS
return statemachine.is_state(entity_id, components.STATE_ON) return statemachine.is_state(entity_id, STATE_ON)
# pylint: disable=unused-argument
def turn_on(bus, entity_id=None, transition_seconds=None): def turn_on(bus, entity_id=None, transition_seconds=None):
""" Turns all or specified light on. """ """ Turns all or specified light on. """
data = {} data = {}
if entity_id: if entity_id:
data[components.ATTR_ENTITY_ID] = entity_id data[ATTR_ENTITY_ID] = entity_id
if transition_seconds: if transition_seconds:
data["transition_seconds"] = transition_seconds data["transition_seconds"] = transition_seconds
bus.call_service(DOMAIN, components.SERVICE_TURN_ON, data) bus.call_service(DOMAIN, SERVICE_TURN_ON, data)
# pylint: disable=unused-argument
def turn_off(bus, entity_id=None, transition_seconds=None): def turn_off(bus, entity_id=None, transition_seconds=None):
""" Turns all or specified light off. """ """ Turns all or specified light off. """
data = {} data = {}
if entity_id: if entity_id:
data[components.ATTR_ENTITY_ID] = entity_id data[ATTR_ENTITY_ID] = entity_id
if transition_seconds: if transition_seconds:
data["transition_seconds"] = transition_seconds data["transition_seconds"] = transition_seconds
bus.call_service(DOMAIN, components.SERVICE_TURN_OFF, data) bus.call_service(DOMAIN, SERVICE_TURN_OFF, data)
def setup(bus, statemachine, light_control): def setup(bus, statemachine, light_control):
@ -64,12 +65,8 @@ def setup(bus, statemachine, light_control):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
entity_ids = {light_id: ENTITY_ID_FORMAT.format(light_id) for light_id ent_to_light = {}
in light_control.light_ids} light_to_ent = {}
if not entity_ids:
logger.error("Light:Found no lights to track")
return
def update_light_state(time): # pylint: disable=unused-argument def update_light_state(time): # pylint: disable=unused-argument
""" Track the state of the lights. """ """ Track the state of the lights. """
@ -82,44 +79,63 @@ def setup(bus, statemachine, light_control):
if should_update: if should_update:
logger.info("Updating light status") logger.info("Updating light status")
update_light_state.last_updated = datetime.now() update_light_state.last_updated = datetime.now()
names = None
status = {light_id: light_control.is_light_on(light_id) states = light_control.get_states()
for light_id in light_control.light_ids}
for light_id, is_light_on in status.items(): for light_id, is_light_on in states.items():
new_state = (components.STATE_ON if is_light_on try:
else components.STATE_OFF) entity_id = light_to_ent[light_id]
except KeyError:
# We have not seen this light before, set it up
statemachine.set_state(entity_ids[light_id], new_state) # Load light names if not loaded this update call
if names is None:
names = light_control.get_names()
ha.track_time_change(bus, update_light_state, second=[0, 30]) name = names.get(
light_id, "Unknown Light {}".format(len(ent_to_light)))
logger.info("Found new light {}".format(name))
entity_id = ENTITY_ID_FORMAT.format(util.slugify(name))
ent_to_light[entity_id] = light_id
light_to_ent[light_id] = entity_id
statemachine.set_state(entity_id,
STATE_ON if is_light_on else STATE_OFF)
# Update light state and discover lights for tracking the group
update_light_state(None) update_light_state(None)
# Track the all lights state # Track all lights in a group
group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids.values()) group.setup(bus, statemachine,
GROUP_NAME_ALL_LIGHTS, light_to_ent.values())
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. """
entity_id = service.data.get(components.ATTR_ENTITY_ID, None) entity_id = service.data.get(ATTR_ENTITY_ID, None)
transition_seconds = service.data.get("transition_seconds", None) transition_seconds = service.data.get("transition_seconds", None)
object_id = util.split_entity_id(entity_id)[1] if entity_id else None if service.service == SERVICE_TURN_ON:
light_control.turn_light_on(ent_to_light.get(entity_id),
if service.service == components.SERVICE_TURN_ON: transition_seconds)
light_control.turn_light_on(object_id, transition_seconds)
else: else:
light_control.turn_light_off(object_id, transition_seconds) light_control.turn_light_off(ent_to_light.get(entity_id),
transition_seconds)
update_light_state(None) update_light_state(None)
# Listen for light on and light off events # Update light state every 30 seconds
bus.register_service(DOMAIN, components.SERVICE_TURN_ON, ha.track_time_change(bus, update_light_state, second=[0, 30])
# Listen for light on and light off service calls
bus.register_service(DOMAIN, SERVICE_TURN_ON,
handle_light_service) handle_light_service)
bus.register_service(DOMAIN, components.SERVICE_TURN_OFF, bus.register_service(DOMAIN, SERVICE_TURN_OFF,
handle_light_service) handle_light_service)
return True return True
@ -133,13 +149,11 @@ class HueLightControl(object):
try: try:
import phue import phue
import socket
except ImportError: except ImportError:
logger.exception( logger.exception(
"HueLightControl:Error while importing dependency phue.") "HueLightControl:Error while importing dependency phue.")
self.success_init = False self.success_init = False
self._light_map = {}
return return
@ -151,45 +165,40 @@ class HueLightControl(object):
"Is phue registered?")) "Is phue registered?"))
self.success_init = False self.success_init = False
self._light_map = {}
return return
self._light_map = {util.slugify(light.name): light for light if len(self._bridge.get_light()) == 0:
in self._bridge.get_light_objects()}
if not self._light_map:
logger.error("HueLightControl:Could not find any lights. ") logger.error("HueLightControl:Could not find any lights. ")
self.success_init = False self.success_init = False
else: else:
self.success_init = True self.success_init = True
@property def get_names(self):
def light_ids(self): """ Return a dict with id mapped to name. """
""" Return a list of light ids. """ try:
return self._light_map.keys() return {int(item[0]): item[1]['name'] for item
in self._bridge.get_light().items()}
def is_light_on(self, light_id=None): except (socket.error, KeyError):
""" Returns if specified or all light are on. """ # socket.error because sometimes we cannot reach Hue
if not light_id: # KeyError if we got unexpected data
return any( return {}
True for light_id in self._light_map.keys()
if self.is_light_on(light_id))
else: def get_states(self):
light_id = self._convert_id(light_id) """ Return a dict with id mapped to boolean is_on. """
if not light_id: # Not valid light_id submitted try:
return False # Light is on if reachable and on
return {int(itm[0]):
itm[1]['state']['reachable'] and itm[1]['state']['on']
for itm in self._bridge.get_api()['lights'].items()}
state = self._bridge.get_light(light_id) except (socket.error, KeyError):
# socket.error because sometimes we cannot reach Hue
try: # KeyError if we got unexpected data
return state['state']['reachable'] and state['state']['on'] return {}
except KeyError:
# If key 'state', 'reachable' or 'on' not exists.
return False
def turn_light_on(self, light_id=None, transition_seconds=None): def turn_light_on(self, light_id=None, transition_seconds=None):
""" Turn the specified or all lights on. """ """ Turn the specified or all lights on. """
@ -199,30 +208,19 @@ class HueLightControl(object):
""" Turn the specified or all lights off. """ """ Turn the specified or all lights off. """
self._turn_light(False, light_id, transition_seconds) self._turn_light(False, light_id, transition_seconds)
def _turn_light(self, turn, light_id=None, transition_seconds=None): def _turn_light(self, turn, light_id, transition_seconds):
""" Helper method to turn lights on or off. """ """ Helper method to turn lights on or off. """
if light_id: if turn:
light_id = self._convert_id(light_id) command = {'on': True, 'xy': [0.5119, 0.4147], 'bri': 164}
if not light_id: # Not valid light id submitted
return
else: else:
light_id = [light.light_id for light in self._light_map.values()] command = {'on': False}
command = {'on': True, 'xy': [0.5119, 0.4147], 'bri': 164} if turn \ if light_id is None:
else {'on': False} light_id = [light.light_id for light in self._bridge.lights]
if transition_seconds: if transition_seconds is not None:
# Transition time is in 1/10th seconds and cannot exceed # Transition time is in 1/10th seconds and cannot exceed
# 900 seconds. # 900 seconds.
command['transitiontime'] = min(9000, transition_seconds * 10) command['transitiontime'] = min(9000, transition_seconds * 10)
self._bridge.set_light(light_id, command) self._bridge.set_light(light_id, command)
def _convert_id(self, light_id):
""" Returns internal light id to be used with phue. """
try:
return self._light_map[light_id].light_id
except KeyError: # if light_id is not a valid key
return None