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.util as util
# String that contains an entity id or a comma seperated list of entity ids
ATTR_ENTITY_ID = 'entity_id'
# String with a friendly name for the entity
ATTR_FRIENDLY_NAME = "friendly_name"
STATE_ON = 'on'
@ -38,7 +41,29 @@ SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause"
SERVICE_MEDIA_NEXT_TRACK = "media_next_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):
@ -46,26 +71,10 @@ def is_on(statemachine, entity_id=None):
If there is no entity id given we will check all. """
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:
domain = util.split_entity_id(entity_id)[0]
# See if we have the module locally cached, else import it
# 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
module = _get_component(domain)
try:
if module.is_on(statemachine, entity_id):
@ -108,6 +117,45 @@ def turn_off(bus, entity_id=None):
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):
""" Setup general services related to homeassistant. """

View file

@ -172,13 +172,14 @@ def setup(bus, statemachine):
def _service_to_entities(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:
cast = casts.get(entity_id)
if entity_ids:
for entity_id in entity_ids:
cast = casts.get(entity_id)
if cast:
yield entity_id, cast
if cast:
yield entity_id, cast
else:
for item in casts.items():

View file

@ -8,17 +8,20 @@ Provides functionality to group devices that can be turned on or off.
import logging
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"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
STATE_ATTR_ENTITY_IDS = "entity_ids"
_GROUP_TYPES = {
"on_off": (components.STATE_ON, components.STATE_OFF),
"home_not_home": (components.STATE_HOME, components.STATE_NOT_HOME)
"on_off": (STATE_ON, STATE_OFF),
"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. """
try:
return \
statemachine.get_state(entity_id).attributes[STATE_ATTR_ENTITY_IDS]
statemachine.get_state(entity_id).attributes[ATTR_ENTITY_ID]
except (AttributeError, KeyError):
# AttributeError if state did not exist
# KeyError if key did not exist in attributes
@ -111,7 +114,7 @@ def setup(bus, statemachine, name, entity_ids):
return False
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
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
# 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):
""" Call components.turn_on for each entity_id from this group. """
for entity_id in get_entity_ids(statemachine,
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)
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):
""" Call components.turn_off for each entity_id in this group. """
for entity_id in get_entity_ids(statemachine,
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)
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.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,
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
@ -157,15 +158,18 @@ def setup(bus, statemachine, light_control):
# Get and validate data
dat = service.data
if ATTR_ENTITY_ID in dat:
light_id = ent_to_light.get(dat[ATTR_ENTITY_ID])
else:
light_id = None
# Convert the entity ids to valid light ids
light_ids = [ent_to_light[entity_id] for entity_id
in extract_entity_ids(statemachine, service)
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)
if service.service == SERVICE_TURN_OFF:
light_control.turn_light_off(light_id, transition)
light_control.turn_light_off(light_ids, transition)
else:
# Processing extra data for turn light on request
@ -201,13 +205,14 @@ def setup(bus, statemachine, light_control):
except (TypeError, ValueError):
# TypeError if color has no len
# 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
if light_id:
update_light_state(light_id)
# Update state of lights touched. If there was only 1 light selected
# then just update that light else update all
if len(light_ids) == 1:
update_light_state(light_ids[0])
else:
update_lights_state(None, True)
@ -329,11 +334,8 @@ class HueLightControl(object):
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. """
if light_id is None:
light_id = self._lights.keys()
command = {'on': True}
if transition is not None:
@ -347,13 +349,10 @@ class HueLightControl(object):
if 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. """
if light_id is None:
light_id = self._lights.keys()
command = {'on': False}
if transition is not None:
@ -361,4 +360,4 @@ class HueLightControl(object):
# 900 seconds.
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.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,
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME)
DOMAIN = 'wemo'
@ -118,13 +119,11 @@ def setup(bus, statemachine):
def _handle_wemo_service(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:
device = ent_to_dev.get(dat[ATTR_ENTITY_ID])
devices = [device] if device is not None else []
else:
if not devices:
devices = ent_to_dev.values()
for device in devices: