Reworked entity id extraction from service calls

This commit is contained in:
Paulus Schoutsen 2014-03-24 20:34:35 -07:00
parent 49e6386794
commit 2890f2d6cc
5 changed files with 116 additions and 66 deletions

View File

@ -20,7 +20,10 @@ import importlib
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
# String that contains an entity id or a comma seperated list of entity ids
ATTR_ENTITY_ID = 'entity_id' ATTR_ENTITY_ID = 'entity_id'
# String with a friendly name for the entity
ATTR_FRIENDLY_NAME = "friendly_name" ATTR_FRIENDLY_NAME = "friendly_name"
STATE_ON = 'on' STATE_ON = 'on'
@ -38,7 +41,29 @@ SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause"
SERVICE_MEDIA_NEXT_TRACK = "media_next_track" SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
SERVICE_MEDIA_PREV_TRACK = "media_prev_track" SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
_LOADED_MOD = {} _LOADED_COMP = {}
def _get_component(component):
""" Returns requested component. Imports it if necessary. """
comps = _LOADED_COMP
# See if we have the module locally cached, else import it
try:
return comps[component]
except KeyError:
# If comps[component] does not exist, import module
try:
comps[component] = importlib.import_module(
'homeassistant.components.'+component)
except ImportError:
# If we got a bogus component the input will fail
comps[component] = None
return comps[component]
def is_on(statemachine, entity_id=None): def is_on(statemachine, entity_id=None):
@ -46,26 +71,10 @@ def is_on(statemachine, entity_id=None):
If there is no entity id given we will check all. """ If there is no entity id given we will check all. """
entity_ids = [entity_id] if entity_id else statemachine.entity_ids entity_ids = [entity_id] if entity_id else statemachine.entity_ids
# Save reference locally because those lookups are way faster
modules = _LOADED_MOD
for entity_id in entity_ids: for entity_id in entity_ids:
domain = util.split_entity_id(entity_id)[0] domain = util.split_entity_id(entity_id)[0]
# See if we have the module locally cached, else import it module = _get_component(domain)
# Does this code look chaotic? Yes!
try:
module = modules[domain]
except KeyError:
# If modules[domain] does not exist, import module
try:
module = modules[domain] = importlib.import_module(
'homeassistant.components.'+domain)
except ImportError:
# If we got a bogus domain the input will fail
module = modules[domain] = None
try: try:
if module.is_on(statemachine, entity_id): if module.is_on(statemachine, entity_id):
@ -108,6 +117,45 @@ def turn_off(bus, entity_id=None):
pass pass
def extract_entity_ids(statemachine, service):
"""
Helper method to extract a list of entity ids from a service call.
Will convert group entity ids to the entity ids it represents.
"""
entity_ids = []
if service.data and ATTR_ENTITY_ID in service.data:
group = _get_component('group')
# Entity ID attr can be a list or a string
service_ent_id = service.data[ATTR_ENTITY_ID]
if isinstance(service_ent_id, list):
ent_ids = service_ent_id
else:
ent_ids = [service_ent_id]
for entity_id in ent_ids:
try:
# If entity_id points at a group, expand it
domain, _ = util.split_entity_id(entity_id)
if domain == group.DOMAIN:
entity_ids.extend(
ent_id for ent_id
in group.get_entity_ids(statemachine, entity_id)
if ent_id not in entity_ids)
else:
if entity_id not in entity_ids:
entity_ids.append(entity_id)
except AttributeError:
# Raised by util.split_entity_id if entity_id is not a string
pass
return entity_ids
def setup(bus): def setup(bus):
""" Setup general services related to homeassistant. """ """ Setup general services related to homeassistant. """

View File

@ -172,9 +172,10 @@ def setup(bus, statemachine):
def _service_to_entities(service): def _service_to_entities(service):
""" Helper method to get entities from service. """ """ Helper method to get entities from service. """
entity_id = service.data.get(components.ATTR_ENTITY_ID) entity_ids = components.extract_entity_ids(statemachine, service)
if entity_id: if entity_ids:
for entity_id in entity_ids:
cast = casts.get(entity_id) cast = casts.get(entity_id)
if cast: if cast:

View File

@ -8,17 +8,20 @@ Provides functionality to group devices that can be turned on or off.
import logging import logging
import homeassistant as ha import homeassistant as ha
import homeassistant.components as components from homeassistant.components import (STATE_ON, STATE_OFF,
STATE_HOME, STATE_NOT_HOME,
SERVICE_TURN_ON, SERVICE_TURN_OFF,
turn_on as comp_turn_on,
turn_off as comp_turn_off,
ATTR_ENTITY_ID)
DOMAIN = "group" DOMAIN = "group"
ENTITY_ID_FORMAT = DOMAIN + ".{}" ENTITY_ID_FORMAT = DOMAIN + ".{}"
STATE_ATTR_ENTITY_IDS = "entity_ids"
_GROUP_TYPES = { _GROUP_TYPES = {
"on_off": (components.STATE_ON, components.STATE_OFF), "on_off": (STATE_ON, STATE_OFF),
"home_not_home": (components.STATE_HOME, components.STATE_NOT_HOME) "home_not_home": (STATE_HOME, STATE_NOT_HOME)
} }
@ -51,7 +54,7 @@ def get_entity_ids(statemachine, entity_id):
""" Get the entity ids that make up this group. """ """ Get the entity ids that make up this group. """
try: try:
return \ return \
statemachine.get_state(entity_id).attributes[STATE_ATTR_ENTITY_IDS] statemachine.get_state(entity_id).attributes[ATTR_ENTITY_ID]
except (AttributeError, KeyError): except (AttributeError, KeyError):
# AttributeError if state did not exist # AttributeError if state did not exist
# KeyError if key did not exist in attributes # KeyError if key did not exist in attributes
@ -111,7 +114,7 @@ def setup(bus, statemachine, name, entity_ids):
return False return False
group_entity_id = ENTITY_ID_FORMAT.format(name) group_entity_id = ENTITY_ID_FORMAT.format(name)
state_attr = {STATE_ATTR_ENTITY_IDS: entity_ids} state_attr = {ATTR_ENTITY_ID: entity_ids}
# pylint: disable=unused-argument # pylint: disable=unused-argument
def update_group_state(entity_id, old_state, new_state): def update_group_state(entity_id, old_state, new_state):
@ -141,28 +144,28 @@ def setup(bus, statemachine, name, entity_ids):
# group.setup is called to setup each group. Only the first time will we # group.setup is called to setup each group. Only the first time will we
# register a turn_on and turn_off method for groups. # register a turn_on and turn_off method for groups.
if not bus.has_service(DOMAIN, components.SERVICE_TURN_ON): if not bus.has_service(DOMAIN, SERVICE_TURN_ON):
def turn_group_on_service(service): def turn_group_on_service(service):
""" Call components.turn_on for each entity_id from this group. """ """ Call components.turn_on for each entity_id from this group. """
for entity_id in get_entity_ids(statemachine, for entity_id in get_entity_ids(statemachine,
service.data.get( service.data.get(
components.ATTR_ENTITY_ID)): ATTR_ENTITY_ID)):
components.turn_on(bus, entity_id) comp_turn_on(bus, entity_id)
bus.register_service(DOMAIN, components.SERVICE_TURN_ON, bus.register_service(DOMAIN, SERVICE_TURN_ON,
turn_group_on_service) turn_group_on_service)
if not bus.has_service(DOMAIN, components.SERVICE_TURN_OFF): if not bus.has_service(DOMAIN, SERVICE_TURN_OFF):
def turn_group_off_service(service): def turn_group_off_service(service):
""" Call components.turn_off for each entity_id in this group. """ """ Call components.turn_off for each entity_id in this group. """
for entity_id in get_entity_ids(statemachine, for entity_id in get_entity_ids(statemachine,
service.data.get( service.data.get(
components.ATTR_ENTITY_ID)): ATTR_ENTITY_ID)):
components.turn_off(bus, entity_id) comp_turn_off(bus, entity_id)
bus.register_service(DOMAIN, components.SERVICE_TURN_OFF, bus.register_service(DOMAIN, SERVICE_TURN_OFF,
turn_group_off_service) turn_group_off_service)
statemachine.set_state(group_entity_id, group_state, state_attr) statemachine.set_state(group_entity_id, group_state, state_attr)

View File

@ -12,7 +12,8 @@ from collections import namedtuple
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
from homeassistant.components import (group, STATE_ON, STATE_OFF, from homeassistant.components import (group, extract_entity_ids,
STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF,
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME) ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
@ -157,15 +158,18 @@ def setup(bus, statemachine, light_control):
# Get and validate data # Get and validate data
dat = service.data dat = service.data
if ATTR_ENTITY_ID in dat: # Convert the entity ids to valid light ids
light_id = ent_to_light.get(dat[ATTR_ENTITY_ID]) light_ids = [ent_to_light[entity_id] for entity_id
else: in extract_entity_ids(statemachine, service)
light_id = None if entity_id in ent_to_light]
if not light_ids:
light_ids = ent_to_light.values()
transition = util.dict_get_convert(dat, ATTR_TRANSITION, int, None) transition = util.dict_get_convert(dat, ATTR_TRANSITION, int, None)
if service.service == SERVICE_TURN_OFF: if service.service == SERVICE_TURN_OFF:
light_control.turn_light_off(light_id, 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
@ -201,13 +205,14 @@ def setup(bus, statemachine, light_control):
except (TypeError, ValueError): except (TypeError, ValueError):
# TypeError if color has no len # TypeError if color has no len
# ValueError if not all values convertable to int # ValueError if not all values convertable to int
color = None pass
light_control.turn_light_on(light_id, transition, bright, color) light_control.turn_light_on(light_ids, transition, bright, color)
# Update state of lights touched # Update state of lights touched. If there was only 1 light selected
if light_id: # then just update that light else update all
update_light_state(light_id) if len(light_ids) == 1:
update_light_state(light_ids[0])
else: else:
update_lights_state(None, True) update_lights_state(None, True)
@ -329,11 +334,8 @@ class HueLightControl(object):
return states return states
def turn_light_on(self, light_id, transition, brightness, xy_color): def turn_light_on(self, light_ids, transition, brightness, xy_color):
""" Turn the specified or all lights on. """ """ Turn the specified or all lights on. """
if light_id is None:
light_id = self._lights.keys()
command = {'on': True} command = {'on': True}
if transition is not None: if transition is not None:
@ -347,13 +349,10 @@ class HueLightControl(object):
if xy_color: if xy_color:
command['xy'] = xy_color command['xy'] = xy_color
self._bridge.set_light(light_id, command) self._bridge.set_light(light_ids, command)
def turn_light_off(self, light_id, transition): def turn_light_off(self, light_ids, transition):
""" Turn the specified or all lights off. """ """ Turn the specified or all lights off. """
if light_id is None:
light_id = self._lights.keys()
command = {'on': False} command = {'on': False}
if transition is not None: if transition is not None:
@ -361,4 +360,4 @@ class HueLightControl(object):
# 900 seconds. # 900 seconds.
command['transitiontime'] = min(9000, transition * 10) command['transitiontime'] = min(9000, transition * 10)
self._bridge.set_light(light_id, command) self._bridge.set_light(light_ids, command)

View File

@ -7,7 +7,8 @@ from datetime import datetime, timedelta
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
from homeassistant.components import (group, STATE_ON, STATE_OFF, from homeassistant.components import (group, extract_entity_ids,
STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF,
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME) ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
DOMAIN = 'wemo' DOMAIN = 'wemo'
@ -118,13 +119,11 @@ def setup(bus, statemachine):
def _handle_wemo_service(service): def _handle_wemo_service(service):
""" Handles calls to the WeMo service. """ """ Handles calls to the WeMo service. """
dat = service.data devices = [ent_to_dev[entity_id] for entity_id
in extract_entity_ids(statemachine, service)
if entity_id in ent_to_dev]
if ATTR_ENTITY_ID in dat: if not devices:
device = ent_to_dev.get(dat[ATTR_ENTITY_ID])
devices = [device] if device is not None else []
else:
devices = ent_to_dev.values() devices = ent_to_dev.values()
for device in devices: for device in devices: