diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index aaeb9057789..e3dd0efefc8 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -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. """ diff --git a/homeassistant/components/chromecast.py b/homeassistant/components/chromecast.py index 7ce36384994..3cf4f08dd63 100644 --- a/homeassistant/components/chromecast.py +++ b/homeassistant/components/chromecast.py @@ -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(): diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index ad2bf8c8d13..be467c4b602 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -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) diff --git a/homeassistant/components/light.py b/homeassistant/components/light.py index 5df19e7dbc5..4d3d179209d 100644 --- a/homeassistant/components/light.py +++ b/homeassistant/components/light.py @@ -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) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index c90f205ab01..a6faba2c4dd 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -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: