mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Reworked lights under the hood, better decoupling and less API calls
This commit is contained in:
parent
7cc9034815
commit
3c79a5ce0a
@ -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
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user