diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index f701ddf2b81..fe3f51a1e46 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -20,16 +20,9 @@ MATCH_ALL = '*' DOMAIN = "homeassistant" -STATE_ON = "on" -STATE_OFF = "off" -STATE_NOT_HOME = 'device_not_home' -STATE_HOME = 'device_home' - -SERVICE_TURN_ON = "turn_on" -SERVICE_TURN_OFF = "turn_off" SERVICE_HOMEASSISTANT_STOP = "stop" -EVENT_HOMEASSISTANT_START = "homeassistant.start" +EVENT_HOMEASSISTANT_START = "homeassistant_start" EVENT_STATE_CHANGED = "state_changed" EVENT_TIME_CHANGED = "time_changed" @@ -78,21 +71,6 @@ def _matcher(subject, pattern): return MATCH_ALL == pattern or subject in pattern -def split_entity_id(entity_id): - """ Splits a state entity_id into domain, object_id. """ - return entity_id.split(".", 1) - - -def filter_entity_ids(entity_ids, domain_filter=None, strip_domain=False): - """ Filter a list of entities based on domain. Setting strip_domain - will only return the object_ids. """ - return [ - split_entity_id(entity_id)[1] if strip_domain else entity_id - for entity_id in entity_ids if - not domain_filter or entity_id.startswith(domain_filter) - ] - - def track_state_change(bus, entity_id, action, from_state=None, to_state=None): """ Helper method to track specific state changes. """ from_state = _process_match_param(from_state) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 96ba112d5ca..9369246b68e 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -134,7 +134,7 @@ def from_config_file(config_path): add_status("Downloader", downloader.setup( bus, get_opt("downloader", "download_dir"))) - add_status("General", general.setup(bus, statemachine)) + add_status("General", general.setup(bus)) if has_section('browser'): add_status("Browser", load_module('browser').setup(bus)) diff --git a/homeassistant/components/chromecast.py b/homeassistant/components/chromecast.py index fcbdcefa801..018f2deae06 100644 --- a/homeassistant/components/chromecast.py +++ b/homeassistant/components/chromecast.py @@ -6,46 +6,46 @@ Provides functionality to interact with Chromecasts. """ import logging -from homeassistant.external import pychromecast - import homeassistant as ha import homeassistant.util as util +from homeassistant.components import general +DOMAIN = 'chromecast' -DOMAIN = "chromecast" - -SERVICE_YOUTUBE_VIDEO = "play_youtube_video" +SERVICE_YOUTUBE_VIDEO = 'play_youtube_video' ENTITY_ID_FORMAT = DOMAIN + '.{}' -STATE_NO_APP = "none" +STATE_NO_APP = 'no_app' -ATTR_FRIENDLY_NAME = "friendly_name" -ATTR_HOST = "host" -ATTR_STATE = "state" -ATTR_OPTIONS = "options" +ATTR_FRIENDLY_NAME = 'friendly_name' +ATTR_HOST = 'host' +ATTR_STATE = 'state' +ATTR_OPTIONS = 'options' -def turn_off(statemachine, cc_id=None): - """ Exits any running app on the specified ChromeCast and shows - idle screen. Will quit all ChromeCasts if nothing specified. """ +def is_on(statemachine, entity_id=None): + """ Returns true if specified ChromeCast entity_id is on. + Will check all chromecasts if no entity_id specified. """ - entity_ids = [ENTITY_ID_FORMAT.format(cc_id)] if cc_id \ - else ha.filter_entity_ids(statemachine.entity_ids, DOMAIN) + entity_ids = [entity_id] if entity_id \ + else util.filter_entity_ids(statemachine.entity_ids, DOMAIN) - for entity_id in entity_ids: - state = statemachine.get_state(entity_id) - - if (state and - (state.state != STATE_NO_APP or - state.state != pychromecast.APP_ID_HOME)): - - pychromecast.quit_app(state.attributes[ATTR_HOST]) + return any(not statemachine.is_state(entity_id, STATE_NO_APP) + for entity_id in entity_ids) def setup(bus, statemachine, host): """ Listen for chromecast events. """ logger = logging.getLogger(__name__) + try: + from homeassistant.external import pychromecast + except ImportError: + logger.exception(("Failed to import pychromecast. " + "Did you maybe not cloned the git submodules?")) + + return False + logger.info("Getting device status") device = pychromecast.get_device_status(host) @@ -53,13 +53,30 @@ def setup(bus, statemachine, host): logger.error("Could not find Chromecast") return False - entity = ENTITY_ID_FORMAT.format(util.slugify( - device.friendly_name)) + entity = ENTITY_ID_FORMAT.format(util.slugify(device.friendly_name)) - bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF, - lambda service: - turn_off(statemachine, - service.data.get("cc_id", None))) + if not bus.has_service(DOMAIN, general.SERVICE_TURN_OFF): + def _turn_off_service(service): + """ Service to exit any running app on the specified ChromeCast and + shows idle screen. Will quit all ChromeCasts if nothing specified. + """ + entity_id = service.data.get(general.ATTR_ENTITY_ID) + + entity_ids = [entity_id] if entity_id \ + else util.filter_entity_ids(statemachine.entity_ids, DOMAIN) + + for entity_id in entity_ids: + state = statemachine.get_state(entity_id) + + try: + pychromecast.quit_app(state.attributes[ATTR_HOST]) + except (AttributeError, KeyError): + # AttributeError: state returned None + # KeyError: ATTR_HOST did not exist + pass + + bus.register_service(DOMAIN, general.SERVICE_TURN_OFF, + _turn_off_service) bus.register_service(DOMAIN, "start_fireplace", lambda service: @@ -74,7 +91,7 @@ def setup(bus, statemachine, host): pychromecast.play_youtube_video( host, service.data['video'])) - def update_chromecast_state(time): # pylint: disable=unused-argument + def _update_chromecast_state(time): # pylint: disable=unused-argument """ Retrieve state of Chromecast and update statemachine. """ logger.info("Updating app status") status = pychromecast.get_app_status(host) @@ -92,8 +109,8 @@ def setup(bus, statemachine, host): else: statemachine.set_state(entity, STATE_NO_APP, {ATTR_HOST: host}) - ha.track_time_change(bus, update_chromecast_state) + ha.track_time_change(bus, _update_chromecast_state) - update_chromecast_state(None) + _update_chromecast_state(None) return True diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 453541080cd..6370d59b510 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -9,6 +9,7 @@ import logging from datetime import datetime, timedelta import homeassistant as ha +import homeassistant.util as util from . import light, sun, device_tracker, general, group @@ -22,8 +23,8 @@ def setup(bus, statemachine, light_group=None): logger = logging.getLogger(__name__) - device_entity_ids = ha.filter_entity_ids(statemachine.entity_ids, - device_tracker.DOMAIN) + device_entity_ids = util.filter_entity_ids(statemachine.entity_ids, + device_tracker.DOMAIN) if not device_entity_ids: logger.error("LightTrigger:No devices found to track") @@ -33,7 +34,7 @@ def setup(bus, statemachine, light_group=None): light_group = light_group or light.GROUP_NAME_ALL_LIGHTS # Get the light IDs from the specified group - light_ids = ha.filter_entity_ids( + light_ids = util.filter_entity_ids( group.get_entity_ids(statemachine, light_group), light.DOMAIN) if not light_ids: @@ -55,7 +56,7 @@ def setup(bus, statemachine, light_group=None): def turn_light_on_before_sunset(light_id): """ Helper function to turn on lights slowly if there are devices home and the light is not on yet. """ - if (device_tracker.is_home(statemachine) and + if (device_tracker.is_on(statemachine) and not light.is_on(statemachine, light_id)): light.turn_on(bus, light_id, LIGHT_TRANSITION_TIME.seconds) @@ -80,18 +81,18 @@ def setup(bus, statemachine, light_group=None): # If the sun is already above horizon # schedule the time-based pre-sun set event - if sun.is_up(statemachine): + if sun.is_on(statemachine): handle_sun_rising(None, None, None) - def handle_device_state_change(entity, old_state, new_state): + def _handle_device_state_change(entity, old_state, new_state): """ Function to handle tracked device state changes. """ lights_are_on = group.is_on(statemachine, light_group) - light_needed = not (lights_are_on or sun.is_up(statemachine)) + light_needed = not (lights_are_on or sun.is_on(statemachine)) # Specific device came home ? if (entity != device_tracker.ENTITY_ID_ALL_DEVICES and - new_state.state == ha.STATE_HOME): + new_state.state == general.STATE_HOME): # These variables are needed for the elif check now = datetime.now() @@ -104,6 +105,8 @@ def setup(bus, statemachine, light_group=None): "Home coming event for {}. Turning lights on". format(entity)) + # Turn on lights directly instead of calling group.turn_on + # So we skip fetching the entity ids again. for light_id in light_ids: light.turn_on(bus, light_id) @@ -127,21 +130,21 @@ def setup(bus, statemachine, light_group=None): # Did all devices leave the house? elif (entity == device_tracker.ENTITY_ID_ALL_DEVICES and - new_state.state == ha.STATE_NOT_HOME and lights_are_on): + new_state.state == general.STATE_NOT_HOME and lights_are_on): logger.info( "Everyone has left but there are devices on. Turning them off") - general.shutdown_devices(bus, statemachine) + light.turn_off(bus, statemachine) # Track home coming of each seperate device for entity in device_entity_ids: - ha.track_state_change(bus, entity, handle_device_state_change, - ha.STATE_NOT_HOME, ha.STATE_HOME) + ha.track_state_change(bus, entity, _handle_device_state_change, + general.STATE_NOT_HOME, general.STATE_HOME) # Track when all devices are gone to shut down lights ha.track_state_change(bus, device_tracker.ENTITY_ID_ALL_DEVICES, - handle_device_state_change, ha.STATE_HOME, - ha.STATE_NOT_HOME) + _handle_device_state_change, general.STATE_HOME, + general.STATE_NOT_HOME) return True diff --git a/homeassistant/components/device_tracker.py b/homeassistant/components/device_tracker.py index e34bf090913..e5de41933b4 100644 --- a/homeassistant/components/device_tracker.py +++ b/homeassistant/components/device_tracker.py @@ -16,9 +16,7 @@ import requests import homeassistant as ha import homeassistant.util as util -import homeassistant.components.group as group - -import homeassistant.external.pynetgear as pynetgear +from homeassistant.components import general, group DOMAIN = "device_tracker" @@ -41,12 +39,11 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) KNOWN_DEVICES_FILE = "known_devices.csv" -def is_home(statemachine, device_id=None): +def is_on(statemachine, entity_id=None): """ Returns if any or specified device is home. """ - entity = ENTITY_ID_FORMAT.format(device_id) if device_id \ - else ENTITY_ID_ALL_DEVICES + entity = entity_id or ENTITY_ID_ALL_DEVICES - return statemachine.is_state(entity, ha.STATE_HOME) + return statemachine.is_state(entity, general.STATE_HOME) # pylint: disable=too-many-instance-attributes @@ -100,30 +97,30 @@ class DeviceTracker(object): now = datetime.now() - temp_tracking_devices = [device for device in self.known_devices - if self.known_devices[device]['track']] + known_dev = self.known_devices + + temp_tracking_devices = [device for device in known_dev + if known_dev[device]['track']] for device in found_devices: # Are we tracking this device? if device in temp_tracking_devices: temp_tracking_devices.remove(device) - self.known_devices[device]['last_seen'] = now + known_dev[device]['last_seen'] = now self.statemachine.set_state( - self.known_devices[device]['entity_id'], ha.STATE_HOME) + known_dev[device]['entity_id'], general.STATE_HOME) # For all devices we did not find, set state to NH # But only if they have been gone for longer then the error time span # Because we do not want to have stuff happening when the device does # not show up for 1 scan beacuse of reboot etc for device in temp_tracking_devices: - if (now - self.known_devices[device]['last_seen'] > - self.error_scanning): + if (now - known_dev[device]['last_seen'] > self.error_scanning): - self.statemachine.set_state( - self.known_devices[device]['entity_id'], - ha.STATE_NOT_HOME) + self.statemachine.set_state(known_dev[device]['entity_id'], + general.STATE_NOT_HOME) # If we come along any unknown devices we will write them to the # known devices file but only if we did not encounter an invalid @@ -131,7 +128,7 @@ class DeviceTracker(object): if not self.invalid_known_devices_file: unknown_devices = [device for device in found_devices - if device not in self.known_devices] + if device not in known_dev] if unknown_devices: try: @@ -151,14 +148,13 @@ class DeviceTracker(object): for device in unknown_devices: # See if the device scanner knows the name - temp_name = \ - self.device_scanner.get_device_name(device) - - name = temp_name if temp_name else "unknown_device" + # else defaults to unknown device + name = (self.device_scanner.get_device_name(device) + or "unknown_device") writer.writerow((device, name, 0)) - self.known_devices[device] = {'name': name, - 'track': False} + known_dev[device] = {'name': name, + 'track': False} except IOError: self.logger.exception(( @@ -380,17 +376,28 @@ class NetgearDeviceScanner(object): """ This class queries a Netgear wireless router using the SOAP-api. """ def __init__(self, host, username, password): - self._api = pynetgear.Netgear(host, username, password) - self.logger = logging.getLogger(__name__) - self.lock = threading.Lock() - self.date_updated = None self.last_results = [] + try: + import homeassistant.external.pynetgear as pynetgear + except ImportError: + self.logger.exception( + ("Netgear:Failed to import pynetgear. " + "Did you maybe not cloned the git submodules?")) + + self.success_init = False + + return + + self._api = pynetgear.Netgear(host, username, password) + self.lock = threading.Lock() + self.logger.info("Netgear:Logging in") if self._api.login(): - self.success_init = self._update_info() + self.success_init = True + self._update_info() else: self.logger.error("Netgear:Failed to Login") @@ -423,6 +430,8 @@ class NetgearDeviceScanner(object): def _update_info(self): """ Retrieves latest information from the Netgear router. Returns boolean if scanning successful. """ + if not self.success_init: + return with self.lock: # if date_updated is None or the date is too old we scan for @@ -436,7 +445,7 @@ class NetgearDeviceScanner(object): self.date_updated = datetime.now() - return True + return else: - return True + return diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader.py index 3c2f4dd6211..2a6b93072f7 100644 --- a/homeassistant/components/downloader.py +++ b/homeassistant/components/downloader.py @@ -31,7 +31,7 @@ def setup(bus, download_path): return False - def download_file(service): + def _download_file(service): """ Downloads file specified in the url. """ try: @@ -78,6 +78,6 @@ def setup(bus, download_path): format(service.data['url'])) bus.register_service(DOMAIN, SERVICE_DOWNLOAD_FILE, - download_file) + _download_file) return True diff --git a/homeassistant/components/general.py b/homeassistant/components/general.py index ddcb37c57aa..b764da83e0a 100644 --- a/homeassistant/components/general.py +++ b/homeassistant/components/general.py @@ -4,23 +4,100 @@ homeassistant.components.general This component contains a service to shut down all devices. """ +import importlib import homeassistant as ha -from . import chromecast, light +import homeassistant.util as util -SERVICE_SHUTDOWN_DEVICES = "shutdown_devices" +ATTR_ENTITY_ID = 'entity_id' + +STATE_ON = "on" +STATE_OFF = "off" +STATE_NOT_HOME = 'device_not_home' +STATE_HOME = 'device_home' + +SERVICE_TURN_ON = "turn_on" +SERVICE_TURN_OFF = "turn_off" + +_LOADED_MOD = {} -def shutdown_devices(bus, statemachine): - """ Tries to shutdown all devices that are currently on. """ - chromecast.turn_off(statemachine) - light.turn_off(bus) +def _get_module(module): + """ Helper function to load a module. """ + try: + return _LOADED_MOD[module] + + except KeyError: + # if module key did not exist in loaded dict + try: + module = _LOADED_MOD[module] = importlib.import_module( + 'homeassistant.components.'+module) + + return module + + except ImportError: + # If module does not exist + return None -def setup(bus, statemachine): - """ Setup services related to homeassistant. """ +def is_on(statemachine, entity_id=None): + """ Loads up the module to call the turn_on method. + If there is no entity id given we will check all. """ + entity_ids = [entity_id] if entity_id else statemachine.entity_ids - bus.register_service(ha.DOMAIN, SERVICE_SHUTDOWN_DEVICES, - lambda service: shutdown_devices(bus, statemachine)) + for entity_id in entity_ids: + domain = util.split_entity_id(entity_id)[0] + + try: + if _get_module(domain).is_on(statemachine, entity_id): + return True + + except AttributeError: + # method is_on does not exist within module + pass + + return False + + +def turn_on(bus, entity_id=None): + """ Turns specified entity on if possible. """ + # If there is no entity_id we do not know which domain to call. + if not entity_id: + return + + domain = util.split_entity_id(entity_id)[0] + + try: + bus.call_service(domain, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}) + except ha.ServiceDoesNotExistError: + # turn_on service does not exist + pass + + +def turn_off(bus, entity_id=None): + """ Turns specified entity off. """ + # If there is no entity_id we do not know which domain to call. + if not entity_id: + return + + domain = util.split_entity_id(entity_id)[0] + + try: + bus.call_service(domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}) + except ha.ServiceDoesNotExistError: + # turn_off service does not exist + pass + + +def setup(bus): + """ Setup general services related to homeassistant. """ + + bus.register_service(ha.DOMAIN, SERVICE_TURN_OFF, + lambda service: + turn_off(bus, service.data.get(ATTR_ENTITY_ID))) + + bus.register_service(ha.DOMAIN, SERVICE_TURN_ON, + lambda service: + turn_on(bus, service.data.get(ATTR_ENTITY_ID))) return True diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 7817d39c235..9c94c4f8b0c 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -8,6 +8,7 @@ Provides functionality to group devices that can be turned on or off. import logging import homeassistant as ha +from homeassistant.components import general as gen DOMAIN = "group" @@ -16,8 +17,8 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}" STATE_ATTR_ENTITY_IDS = "entity_ids" _GROUP_TYPES = { - "on_off": (ha.STATE_ON, ha.STATE_OFF), - "home_not_home": (ha.STATE_HOME, ha.STATE_NOT_HOME) + "on_off": (gen.STATE_ON, gen.STATE_OFF), + "home_not_home": (gen.STATE_HOME, gen.STATE_NOT_HOME) } @@ -30,9 +31,9 @@ def _get_group_type(state): return None -def is_on(statemachine, group): +def is_on(statemachine, entity_id): """ Returns if the group state is in its ON-state. """ - state = statemachine.get_state(group) + state = statemachine.get_state(entity_id) if state: group_type = _get_group_type(state.state) @@ -46,17 +47,18 @@ def is_on(statemachine, group): return False -def get_entity_ids(statemachine, group): +def get_entity_ids(statemachine, entity_id): """ Get the entity ids that make up this group. """ try: - return statemachine.get_state(group).attributes[STATE_ATTR_ENTITY_IDS] + return \ + statemachine.get_state(entity_id).attributes[STATE_ATTR_ENTITY_IDS] except (AttributeError, KeyError): # AttributeError if state did not exist # KeyError if key did not exist in attributes return [] -# pylint: disable=too-many-branches +# pylint: disable=too-many-branches, too-many-locals def setup(bus, statemachine, name, entity_ids): """ Sets up a group state that is the combined state of several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """ @@ -136,6 +138,33 @@ def setup(bus, statemachine, name, entity_ids): for entity_id in entity_ids: ha.track_state_change(bus, entity_id, _update_group_state) + # 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, gen.SERVICE_TURN_ON): + def _turn_group_on_service(service): + """ Call general.turn_on for each entity_id from this group. """ + for entity_id in get_entity_ids(statemachine, + service.data.get( + gen.ATTR_ENTITY_ID)): + + gen.turn_on(bus, entity_id) + + bus.register_service(DOMAIN, gen.SERVICE_TURN_ON, + _turn_group_on_service) + + if not bus.has_service(DOMAIN, gen.SERVICE_TURN_OFF): + def _turn_group_off_service(service): + """ Call general.turn_off for each entity_id from this group. """ + for entity_id in get_entity_ids(statemachine, + service.data.get( + gen.ATTR_ENTITY_ID)): + + gen.turn_off(bus, entity_id) + + bus.register_service(DOMAIN, gen.SERVICE_TURN_OFF, + _turn_group_off_service) + statemachine.set_state(group_entity_id, group_state, state_attr) return True diff --git a/homeassistant/components/httpinterface/__init__.py b/homeassistant/components/httpinterface/__init__.py index 09f72a31a47..84f0b63a31e 100644 --- a/homeassistant/components/httpinterface/__init__.py +++ b/homeassistant/components/httpinterface/__init__.py @@ -592,6 +592,10 @@ class RequestHandler(BaseHTTPRequestHandler): self._message("Service {}/{} called.".format(domain, service)) + except ha.ServiceDoesNotExistError: + # If the service does not exist + self._message('Service does not exist', HTTP_BAD_REQUEST) + except KeyError: # Occurs if domain or service does not exist in data self._message("No domain or service received.", HTTP_BAD_REQUEST) diff --git a/homeassistant/components/light.py b/homeassistant/components/light.py index bcbb7f94d3d..74b5880e57d 100644 --- a/homeassistant/components/light.py +++ b/homeassistant/components/light.py @@ -10,7 +10,7 @@ from datetime import datetime, timedelta import homeassistant as ha import homeassistant.util as util -import homeassistant.components.group as group +from homeassistant.components import general, group DOMAIN = "light" @@ -27,33 +27,35 @@ def is_on(statemachine, entity_id=None): """ Returns if the lights are on based on the statemachine. """ entity_id = entity_id or ENTITY_ID_ALL_LIGHTS - return statemachine.is_state(entity_id, ha.STATE_ON) + return statemachine.is_state(entity_id, general.STATE_ON) +# pylint: disable=unused-argument def turn_on(bus, entity_id=None, transition_seconds=None): """ Turns all or specified light on. """ data = {} if entity_id: - data["light_id"] = ha.split_entity_id(entity_id)[1] + data[general.ATTR_ENTITY_ID] = entity_id if transition_seconds: data["transition_seconds"] = transition_seconds - bus.call_service(DOMAIN, ha.SERVICE_TURN_ON, data) + bus.call_service(DOMAIN, general.SERVICE_TURN_ON, data) +# pylint: disable=unused-argument def turn_off(bus, entity_id=None, transition_seconds=None): """ Turns all or specified light off. """ data = {} if entity_id: - data["light_id"] = ha.split_entity_id(entity_id)[1] + data[general.ATTR_ENTITY_ID] = entity_id if transition_seconds: data["transition_seconds"] = transition_seconds - bus.call_service(DOMAIN, ha.SERVICE_TURN_OFF, data) + bus.call_service(DOMAIN, general.SERVICE_TURN_OFF, data) def setup(bus, statemachine, light_control): @@ -61,6 +63,13 @@ def setup(bus, statemachine, light_control): logger = logging.getLogger(__name__) + entity_ids = {light_id: ENTITY_ID_FORMAT.format(light_id) for light_id + in light_control.light_ids} + + if not entity_ids: + logger.error("Light:Found no lights to track") + return + def update_light_state(time): # pylint: disable=unused-argument """ Track the state of the lights. """ try: @@ -79,39 +88,36 @@ def setup(bus, statemachine, light_control): for light_id in light_control.light_ids} for light_id, state in status.items(): - entity_id = ENTITY_ID_FORMAT.format(light_id) + new_state = general.STATE_ON if state else general.STATE_OFF - new_state = ha.STATE_ON if state else ha.STATE_OFF - - statemachine.set_state(entity_id, new_state) + statemachine.set_state(entity_ids[light_id], new_state) ha.track_time_change(bus, update_light_state, second=[0, 30]) update_light_state(None) # Track the all lights state - entity_ids = [ENTITY_ID_FORMAT.format(light_id) for light_id - in light_control.light_ids] - - group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids) + group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids.values()) def handle_light_service(service): """ Hande a turn light on or off service call. """ - light_id = service.data.get("light_id", None) + entity_id = service.data.get(general.ATTR_ENTITY_ID, None) transition_seconds = service.data.get("transition_seconds", None) - if service.service == ha.SERVICE_TURN_ON: - light_control.turn_light_on(light_id, transition_seconds) + object_id = util.split_entity_id(entity_id)[1] if entity_id else None + + if service.service == general.SERVICE_TURN_ON: + light_control.turn_light_on(object_id, transition_seconds) else: - light_control.turn_light_off(light_id, transition_seconds) + light_control.turn_light_off(object_id, transition_seconds) update_light_state(None) # Listen for light on and light off events - bus.register_service(DOMAIN, ha.SERVICE_TURN_ON, + bus.register_service(DOMAIN, general.SERVICE_TURN_ON, handle_light_service) - bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF, + bus.register_service(DOMAIN, general.SERVICE_TURN_OFF, handle_light_service) return True diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index aae76e7be3d..4c57a513fc7 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -10,7 +10,7 @@ from datetime import timedelta import homeassistant as ha import homeassistant.util as util -ENTITY_ID = "weather.sun" +ENTITY_ID = "sun.sun" STATE_ABOVE_HORIZON = "above_horizon" STATE_BELOW_HORIZON = "below_horizon" @@ -19,9 +19,11 @@ STATE_ATTR_NEXT_RISING = "next_rising" STATE_ATTR_NEXT_SETTING = "next_setting" -def is_up(statemachine): +def is_on(statemachine, entity_id=None): """ Returns if the sun is currently up based on the statemachine. """ - return statemachine.is_state(ENTITY_ID, STATE_ABOVE_HORIZON) + entity_id = entity_id or ENTITY_ID + + return statemachine.is_state(entity_id, STATE_ABOVE_HORIZON) def next_setting(statemachine): diff --git a/homeassistant/remote.py b/homeassistant/remote.py index fcaee2d28d5..1708a228a6a 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -6,7 +6,7 @@ A module containing drop in replacements for core parts that will interface with a remote instance of home assistant. If a connection error occurs while communicating with the API a -HomeAssistantException will be raised. +HomeAssistantError will be raised. """ import threading @@ -44,7 +44,7 @@ def _setup_call_api(host, port, api_password): except requests.exceptions.ConnectionError: logging.getLogger(__name__).exception("Error connecting to server") - raise ha.HomeAssistantException("Error connecting to server") + raise ha.HomeAssistantError("Error connecting to server") return _call_api @@ -85,17 +85,17 @@ class Bus(ha.Bus): return data['services'] else: - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result (3): {}.".format(req.text)) except ValueError: # If req.json() can't parse the json self.logger.exception("Bus:Got unexpected result") - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result: {}".format(req.text)) except KeyError: # If not all expected keys are in the returned JSON self.logger.exception("Bus:Got unexpected result (2)") - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result (2): {}".format(req.text)) @property @@ -110,17 +110,17 @@ class Bus(ha.Bus): return data['event_listeners'] else: - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result (3): {}.".format(req.text)) except ValueError: # If req.json() can't parse the json self.logger.exception("Bus:Got unexpected result") - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result: {}".format(req.text)) except KeyError: # If not all expected keys are in the returned JSON self.logger.exception("Bus:Got unexpected result (2)") - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result (2): {}".format(req.text)) def call_service(self, domain, service, service_data=None): @@ -141,7 +141,11 @@ class Bus(ha.Bus): req.status_code, req.text) self.logger.error("Bus:{}".format(error)) - raise ha.HomeAssistantException(error) + + if req.status_code == 400: + raise ha.ServiceDoesNotExistError(error) + else: + raise ha.HomeAssistantError(error) def register_service(self, domain, service, service_callback): """ Not implemented for remote bus. @@ -166,7 +170,7 @@ class Bus(ha.Bus): req.status_code, req.text) self.logger.error("Bus:{}".format(error)) - raise ha.HomeAssistantException(error) + raise ha.HomeAssistantError(error) def listen_event(self, event_type, listener): """ Not implemented for remote bus. @@ -251,11 +255,11 @@ class StateMachine(ha.StateMachine): req.status_code, req.text) self.logger.error("StateMachine:{}".format(error)) - raise ha.HomeAssistantException(error) + raise ha.HomeAssistantError(error) except requests.exceptions.ConnectionError: self.logger.exception("StateMachine:Error connecting to server") - raise ha.HomeAssistantException("Error connecting to server") + raise ha.HomeAssistantError("Error connecting to server") finally: self.lock.release() @@ -277,19 +281,19 @@ class StateMachine(ha.StateMachine): return None else: - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result (3): {}.".format(req.text)) except requests.exceptions.ConnectionError: self.logger.exception("StateMachine:Error connecting to server") - raise ha.HomeAssistantException("Error connecting to server") + raise ha.HomeAssistantError("Error connecting to server") except ValueError: # If req.json() can't parse the json self.logger.exception("StateMachine:Got unexpected result") - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result: {}".format(req.text)) except KeyError: # If not all expected keys are in the returned JSON self.logger.exception("StateMachine:Got unexpected result (2)") - raise ha.HomeAssistantException( + raise ha.HomeAssistantError( "Got unexpected result (2): {}".format(req.text)) diff --git a/homeassistant/util.py b/homeassistant/util.py index 22ce6dba858..af3f54d7812 100644 --- a/homeassistant/util.py +++ b/homeassistant/util.py @@ -38,3 +38,18 @@ def str_to_datetime(dt_str): return datetime.datetime.strptime(dt_str, DATE_STR_FORMAT) except ValueError: # If dt_str did not match our format return None + + +def split_entity_id(entity_id): + """ Splits a state entity_id into domain, object_id. """ + return entity_id.split(".", 1) + + +def filter_entity_ids(entity_ids, domain_filter=None, strip_domain=False): + """ Filter a list of entities based on domain. Setting strip_domain + will only return the object_ids. """ + return [ + split_entity_id(entity_id)[1] if strip_domain else entity_id + for entity_id in entity_ids if + not domain_filter or entity_id.startswith(domain_filter) + ]