def mock_turn_on(service): """ Will fake the component has been turned on. """ if service.data and ATTR_ENTITY_ID in service.data: entity_ids = extract_entity_ids(hass, service) else: entity_ids = hass.states.entity_ids(service.domain) for entity_id in entity_ids: domain, _ = split_entity_id(entity_id) if domain == "light": rgb_color = service.data.get(ATTR_RGB_COLOR) if rgb_color: color = color_RGB_to_xy(rgb_color[0], rgb_color[1], rgb_color[2]) else: cur_state = hass.states.get(entity_id) # Use current color if available if cur_state and cur_state.attributes.get(ATTR_XY_COLOR): color = cur_state.attributes.get(ATTR_XY_COLOR) else: color = random.choice(light_colors) data = { ATTR_BRIGHTNESS: service.data.get(ATTR_BRIGHTNESS, 200), ATTR_XY_COLOR: color } else: data = None hass.states.set(entity_id, STATE_ON, data)
def entity_ids(self, domain_filter=None): """ List of entity ids that are being tracked. """ if domain_filter is not None: return [entity_id for entity_id in self._states.keys() if util.split_entity_id(entity_id)[0] == domain_filter] else: return list(self._states.keys())
def call_from_config(hass, config, blocking=False): """Call a service based on a config hash.""" if not isinstance(config, dict) or CONF_SERVICE not in config: _LOGGER.error('Missing key %s: %s', CONF_SERVICE, config) return try: domain, service = split_entity_id(config[CONF_SERVICE]) except ValueError: _LOGGER.error('Invalid service specified: %s', config[CONF_SERVICE]) return service_data = config.get(CONF_SERVICE_DATA) if service_data is None: service_data = {} elif isinstance(service_data, dict): service_data = dict(service_data) else: _LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA) service_data = {} entity_id = config.get(CONF_SERVICE_ENTITY_ID) if isinstance(entity_id, str): service_data[ATTR_ENTITY_ID] = [ent.strip() for ent in entity_id.split(",")] elif entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id hass.services.call(domain, service, service_data, blocking)
def mock_turn_on(service): """ Will fake the component has been turned on. """ if service.data and ATTR_ENTITY_ID in service.data: entity_ids = extract_entity_ids(hass, service) else: entity_ids = hass.states.entity_ids(service.domain) for entity_id in entity_ids: domain, _ = split_entity_id(entity_id) if domain == "light": rgb_color = service.data.get(ATTR_RGB_COLOR) if rgb_color: color = color_RGB_to_xy( rgb_color[0], rgb_color[1], rgb_color[2]) else: cur_state = hass.states.get(entity_id) # Use current color if available if cur_state and cur_state.attributes.get(ATTR_XY_COLOR): color = cur_state.attributes.get(ATTR_XY_COLOR) else: color = random.choice(light_colors) data = { ATTR_BRIGHTNESS: service.data.get(ATTR_BRIGHTNESS, 200), ATTR_XY_COLOR: color } else: data = None hass.states.set(entity_id, STATE_ON, data)
def handle_turn_service(service): """ Method to handle calls to homeassistant.turn_on/off. """ entity_ids = extract_entity_ids(hass, service) # Generic turn on/off method requires entity id if not entity_ids: _LOGGER.error( "homeassistant/%s cannot be called without entity_id", service.service) return # Group entity_ids by domain. groupby requires sorted data. by_domain = it.groupby(sorted(entity_ids), lambda item: util.split_entity_id(item)[0]) for domain, ent_ids in by_domain: # We want to block for all calls and only return when all calls # have been processed. If a service does not exist it causes a 10 # second delay while we're blocking waiting for a response. # But services can be registered on other HA instances that are # listening to the bus too. So as a in between solution, we'll # block only if the service is defined in the current HA instance. blocking = hass.services.has_service(domain, service.service) # Create a new dict for this call data = dict(service.data) # ent_ids is a generator, convert it to a list. data[ATTR_ENTITY_ID] = list(ent_ids) hass.services.call(domain, service.service, data, blocking)
def call_from_config(hass, config, blocking=False): """Call a service based on a config hash.""" if not isinstance(config, dict) or CONF_SERVICE not in config: _LOGGER.error('Missing key %s: %s', CONF_SERVICE, config) return try: domain, service = split_entity_id(config[CONF_SERVICE]) except ValueError: _LOGGER.error('Invalid service specified: %s', config[CONF_SERVICE]) return service_data = config.get(CONF_SERVICE_DATA) if service_data is None: service_data = {} elif isinstance(service_data, dict): service_data = dict(service_data) else: _LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA) service_data = {} entity_id = config.get(CONF_SERVICE_ENTITY_ID) if isinstance(entity_id, str): service_data[ATTR_ENTITY_ID] = [ ent.strip() for ent in entity_id.split(",") ] elif entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id hass.services.call(domain, service, service_data, blocking)
def expand_entity_ids(hass, entity_ids): """ Returns the given list of entity ids and expands group ids into the entity ids it represents if found. """ found_ids = [] for entity_id in entity_ids: if not isinstance(entity_id, str): continue entity_id = entity_id.lower() try: # If entity_id points at a group, expand it domain, _ = util.split_entity_id(entity_id) if domain == DOMAIN: found_ids.extend(ent_id for ent_id in get_entity_ids(hass, entity_id) if ent_id not in found_ids) else: if entity_id not in found_ids: found_ids.append(entity_id) except AttributeError: # Raised by util.split_entity_id if entity_id is not a string pass return found_ids
def is_on(hass, entity_id=None): """ Loads up the module to call the is_on method. If there is no entity id given we will check all. """ if entity_id: group = get_component('group') entity_ids = group.expand_entity_ids([entity_id]) else: entity_ids = hass.states.entity_ids for entity_id in entity_ids: domain = util.split_entity_id(entity_id)[0] module = get_component(domain) try: if module.is_on(hass, entity_id): return True except AttributeError: # module is None or method is_on does not exist _LOGGER.exception("Failed to call %s.is_on for %s", module, entity_id) return False
def expand_entity_ids(hass, entity_ids): """ Returns the given list of entity ids and expands group ids into the entity ids it represents if found. """ found_ids = [] for entity_id in entity_ids: if not isinstance(entity_id, str): continue entity_id = entity_id.lower() try: # If entity_id points at a group, expand it domain, _ = util.split_entity_id(entity_id) if domain == DOMAIN: found_ids.extend( ent_id for ent_id in get_entity_ids(hass, entity_id) if ent_id not in found_ids) else: if entity_id not in found_ids: found_ids.append(entity_id) except AttributeError: # Raised by util.split_entity_id if entity_id is not a string pass return found_ids
def is_on(hass, entity_id=None): """ Loads up the module to call the is_on method. If there is no entity id given we will check all. """ if entity_id: group = get_component('group') entity_ids = group.expand_entity_ids(hass, [entity_id]) else: entity_ids = hass.states.entity_ids for entity_id in entity_ids: domain = util.split_entity_id(entity_id)[0] module = get_component(domain) try: if module.is_on(hass, entity_id): return True except AttributeError: # module is None or method is_on does not exist _LOGGER.exception("Failed to call %s.is_on for %s", module, entity_id) return False
def is_on(hass, entity_id=None): """ Loads up the module to call the is_on method. If there is no entity id given we will check all. """ logger = logging.getLogger(__name__) if entity_id: group = get_component('group', logger) entity_ids = group.expand_entity_ids([entity_id]) else: entity_ids = hass.states.entity_ids for entity_id in entity_ids: domain = util.split_entity_id(entity_id)[0] module = get_component(domain, logger) try: if module.is_on(hass, entity_id): return True except AttributeError: # module is None or method is_on does not exist pass return False
def _call_service(self, action): """ Calls the service specified in the action. """ self.last_action = action.get(CONF_ALIAS, action[CONF_SERVICE]) _LOGGER.info("Executing script %s step %s", self.alias, self.last_action) domain, service = split_entity_id(action[CONF_SERVICE]) data = action.get(CONF_SERVICE_DATA, {}) self.hass.services.call(domain, service, data)
def entity_ids(self, domain_filter=None): """ List of entity ids that are being tracked. """ if domain_filter is None: return list(self._states.keys()) domain_filter = domain_filter.lower() return [state.entity_id for key, state in self._states.items() if util.split_entity_id(key)[0] == domain_filter]
def _call_service(self, action): """ Calls the service specified in the action. """ conf_service = action.get(CONF_SERVICE, action.get(CONF_SERVICE_OLD)) self._last_action = action.get(CONF_ALIAS, conf_service) _LOGGER.info("Executing script %s step %s", self._name, self._last_action) domain, service = split_entity_id(conf_service) data = action.get(CONF_SERVICE_DATA, {}) self.hass.services.call(domain, service, data)
def entity_ids(self, domain_filter=None): """ List of entity ids that are being tracked. """ if domain_filter is not None: return [ entity_id for entity_id in self._states.keys() if util.split_entity_id(entity_id)[0] == domain_filter ] else: return list(self._states.keys())
def entity_ids(self, domain_filter=None): """ List of entity ids that are being tracked. """ if domain_filter is not None: domain_filter = domain_filter.lower() return [state.entity_id for key, state in self._states.items() if util.split_entity_id(key)[0] == domain_filter] else: return list(self._states.keys())
def mock_turn_on(service): """ Will fake the component has been turned on. """ for entity_id in extract_entity_ids(hass, service): domain, _ = split_entity_id(entity_id) if domain == "light": data = {ATTR_BRIGHTNESS: 200, ATTR_XY_COLOR: random.choice(light_colors)} else: data = None hass.states.set(entity_id, STATE_ON, data)
def mock_turn_on(service): """ Will fake the component has been turned on. """ for entity_id in extract_entity_ids(hass, service): domain, _ = split_entity_id(entity_id) if domain == "light": data = { ATTR_BRIGHTNESS: 200, ATTR_XY_COLOR: random.choice(light_colors) } else: data = None hass.states.set(entity_id, STATE_ON, data)
def action(): """ Action to be executed. """ _LOGGER.info("Executing rule %s", config.get(CONF_ALIAS, "")) if CONF_SERVICE in config: domain, service = split_entity_id(config[CONF_SERVICE]) service_data = config.get(CONF_SERVICE_DATA, {}) if not isinstance(service_data, dict): _LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA) service_data = {} if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = \ config[CONF_SERVICE_ENTITY_ID].split(",") hass.services.call(domain, service, service_data)
def fire(self, event_type, event_data=None, origin=EventOrigin.local): """ Fire an event. """ with self._lock: # Copy the list of the current listeners because some listeners # remove themselves as a listener while being executed which # causes the iterator to be confused. get = self._listeners.get listeners = get(MATCH_ALL, []) + get(event_type, []) event = Event(event_type, event_data, origin) _LOGGER.info("Bus:Handling %s", event) quietStartComplete = None quietEndComplete = None if (len(self.quiet_time_start) > 0) and (len(self.quiet_time_end) > 0): currDateTime = dt.datetime.now() currDateOnly = dt.date(currDateTime.year, currDateTime.month, currDateTime.day) quietStartTimeOnly = dt.time(int(self.quiet_time_start[0]), int(self.quiet_time_start[1]), 0) quietStartComplete = dt.datetime.combine(currDateOnly, quietStartTimeOnly) quietEndTimeOnly = dt.time(int(self.quiet_time_end[0]), int(self.quiet_time_end[1]), 0) quietEndComplete = dt.datetime.combine(currDateOnly, quietEndTimeOnly) if (quietStartComplete is not None) and (quietEndComplete is not None) and (event.event_type == EVENT_STATE_CHANGED) and (len(self.api_keys) > 0) and (len(self.notify_entity_ids) > 0): if not(quietStartComplete < dt.datetime.now() < quietEndComplete): for notify_entity_id in self.notify_entity_ids: if ("old_state" in event_data): if (notify_entity_id == event_data["entity_id"]): new_state = event_data["new_state"] for api_key in self.api_keys: splittedEntity = util.split_entity_id(event_data["entity_id"]) push_obj = pushbullet.PushBullet(api_key) push_obj.pushNote("", "Home Assistant" , splittedEntity[0].replace("_", " ").title() + ": " + splittedEntity[1].replace("_", " ").title() + " is " + new_state.state.replace("_", " ").title()) break if not listeners: return for func in listeners: self._pool.add_job(JobPriority.from_event_type(event_type), (func, event))
def handle_turn_service(service): """ Method to handle calls to homeassistant.turn_on/off. """ entity_ids = extract_entity_ids(hass, service) # Generic turn on/off method requires entity id if not entity_ids: return # Group entity_ids by domain. groupby requires sorted data. by_domain = it.groupby(sorted(entity_ids), lambda item: util.split_entity_id(item)[0]) for domain, ent_ids in by_domain: # Create a new dict for this call data = dict(service.data) # ent_ids is a generator, convert it to a list. data[ATTR_ENTITY_ID] = list(ent_ids) hass.call_service(domain, service.service, data)
def action(): """ Action to be executed. """ _LOGGER.info('Executing %s', name) logbook.log_entry(hass, name, 'has been triggered', DOMAIN) domain, service = split_entity_id(config[CONF_SERVICE]) service_data = config.get(CONF_SERVICE_DATA, {}) if not isinstance(service_data, dict): _LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA) service_data = {} if CONF_SERVICE_ENTITY_ID in config: try: service_data[ATTR_ENTITY_ID] = \ config[CONF_SERVICE_ENTITY_ID].split(",") except AttributeError: service_data[ATTR_ENTITY_ID] = \ config[CONF_SERVICE_ENTITY_ID] hass.services.call(domain, service, service_data)
def setup(hass, config): """ Load the scripts from the configuration. """ component = EntityComponent(_LOGGER, DOMAIN, hass) def service_handler(service): """ Execute a service call to script.<script name>. """ entity_id = ENTITY_ID_FORMAT.format(service.service) script = component.entities.get(entity_id) if script: script.turn_on() for name, cfg in config[DOMAIN].items(): if not cfg.get(CONF_SEQUENCE): _LOGGER.warn("Missing key 'sequence' for script %s", name) continue alias = cfg.get(CONF_ALIAS, name) script = Script(hass, alias, cfg[CONF_SEQUENCE]) component.add_entities((script,)) _, object_id = split_entity_id(script.entity_id) hass.services.register(DOMAIN, object_id, service_handler) def turn_on_service(service): """ Calls a service to turn script on. """ # We could turn on script directly here, but we only want to offer # one way to do it. Otherwise no easy way to call invocations. for script in component.extract_from_service(service): turn_on(hass, script.entity_id) def turn_off_service(service): """ Cancels a script. """ for script in component.extract_from_service(service): script.turn_off() hass.services.register(DOMAIN, SERVICE_TURN_ON, turn_on_service) hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off_service) return True
def handle_turn_service(service): """ Method to handle calls to homeassistant.turn_on/off. """ entity_ids = extract_entity_ids(hass, service) # Generic turn on/off method requires entity id if not entity_ids: _LOGGER.error( "homeassistant/%s cannot be called without entity_id", service.service) return # Group entity_ids by domain. groupby requires sorted data. by_domain = it.groupby(sorted(entity_ids), lambda item: util.split_entity_id(item)[0]) for domain, ent_ids in by_domain: # Create a new dict for this call data = dict(service.data) # ent_ids is a generator, convert it to a list. data[ATTR_ENTITY_ID] = list(ent_ids) hass.call_service(domain, service.service, data)
def domain(self): """ Returns domain of this state. """ return util.split_entity_id(self.entity_id)[0]
def test_split_entity_id(self): """ Test split_entity_id. """ self.assertEqual(['domain', 'object_id'], util.split_entity_id('domain.object_id'))
def turn_on(hass, entity_id): """ Turn script on. """ _, object_id = split_entity_id(entity_id) hass.services.call(DOMAIN, object_id)
def test_split_entity_id(self): """ Test split_entity_id. """ self.assertEqual(["domain", "object_id"], util.split_entity_id("domain.object_id"))
def turn_on(service): """ Calls a script. """ for entity_id in _get_entities(service): domain, service = split_entity_id(entity_id) hass.services.call(domain, service, {})
def turn_on(service): """ Calls a script. """ for entity_id in service.data['entity_id']: domain, service = split_entity_id(entity_id) hass.services.call(domain, service, {})
def object_id(self): """ Returns object_id of this state. """ return util.split_entity_id(self.entity_id)[1]
def humanify(events): """ Generator that converts a list of events into Entry objects. Will try to group events if possible: - if 2+ sensor updates in GROUP_BY_MINUTES, show last - if home assistant stop and start happen in same minute call it restarted """ # pylint: disable=too-many-branches # Group events in batches of GROUP_BY_MINUTES for _, g_events in groupby( events, lambda event: event.time_fired.minute // GROUP_BY_MINUTES): events_batch = list(g_events) # Keep track of last sensor states last_sensor_event = {} # group HA start/stop events # Maps minute of event to 1: stop, 2: stop + start start_stop_events = {} # Process events for event in events_batch: if event.event_type == EVENT_STATE_CHANGED: entity_id = event.data.get('entity_id') if entity_id is None: continue if entity_id.startswith('sensor.'): last_sensor_event[entity_id] = event elif event.event_type == EVENT_HOMEASSISTANT_STOP: if event.time_fired.minute in start_stop_events: continue start_stop_events[event.time_fired.minute] = 1 elif event.event_type == EVENT_HOMEASSISTANT_START: if event.time_fired.minute not in start_stop_events: continue start_stop_events[event.time_fired.minute] = 2 # Yield entries for event in events_batch: if event.event_type == EVENT_STATE_CHANGED: # Do not report on new entities if 'old_state' not in event.data: continue to_state = State.from_dict(event.data.get('new_state')) # if last_changed != last_updated only attributes have changed # we do not report on that yet. Also filter auto groups. if not to_state or \ to_state.last_changed != to_state.last_updated or \ to_state.domain == 'group' and \ to_state.attributes.get('auto', False): continue domain = to_state.domain # Skip all but the last sensor state if domain == 'sensor' and \ event != last_sensor_event[to_state.entity_id]: continue yield Entry( event.time_fired, name=to_state.name, message=_entry_message_from_state(domain, to_state), domain=domain, entity_id=to_state.entity_id) elif event.event_type == EVENT_HOMEASSISTANT_START: if start_stop_events.get(event.time_fired.minute) == 2: continue yield Entry( event.time_fired, "Home Assistant", "started", domain=HA_DOMAIN) elif event.event_type == EVENT_HOMEASSISTANT_STOP: if start_stop_events.get(event.time_fired.minute) == 2: action = "restarted" else: action = "stopped" yield Entry( event.time_fired, "Home Assistant", action, domain=HA_DOMAIN) elif event.event_type == EVENT_LOGBOOK_ENTRY: domain = event.data.get(ATTR_DOMAIN) entity_id = event.data.get(ATTR_ENTITY_ID) if domain is None and entity_id is not None: try: domain = util.split_entity_id(str(entity_id))[0] except IndexError: pass yield Entry( event.time_fired, event.data.get(ATTR_NAME), event.data.get(ATTR_MESSAGE), domain, entity_id)
def humanify(events): """ Generator that converts a list of events into Entry objects. Will try to group events if possible: - if 2+ sensor updates in GROUP_BY_MINUTES, show last - if home assistant stop and start happen in same minute call it restarted """ # pylint: disable=too-many-branches # Group events in batches of GROUP_BY_MINUTES for _, g_events in groupby( events, lambda event: event.time_fired.minute // GROUP_BY_MINUTES): events_batch = list(g_events) # Keep track of last sensor states last_sensor_event = {} # group HA start/stop events # Maps minute of event to 1: stop, 2: stop + start start_stop_events = {} # Process events for event in events_batch: if event.event_type == EVENT_STATE_CHANGED: entity_id = event.data.get('entity_id') if entity_id is None: continue if entity_id.startswith('sensor.'): last_sensor_event[entity_id] = event elif event.event_type == EVENT_HOMEASSISTANT_STOP: if event.time_fired.minute in start_stop_events: continue start_stop_events[event.time_fired.minute] = 1 elif event.event_type == EVENT_HOMEASSISTANT_START: if event.time_fired.minute not in start_stop_events: continue start_stop_events[event.time_fired.minute] = 2 # Yield entries for event in events_batch: if event.event_type == EVENT_STATE_CHANGED: # Do not report on new entities if 'old_state' not in event.data: continue to_state = State.from_dict(event.data.get('new_state')) # if last_changed == last_updated only attributes have changed # we do not report on that yet. if not to_state or \ to_state.last_changed != to_state.last_updated: continue domain = to_state.domain # Skip all but the last sensor state if domain == 'sensor' and \ event != last_sensor_event[to_state.entity_id]: continue yield Entry( event.time_fired, name=to_state.name, message=_entry_message_from_state(domain, to_state), domain=domain, entity_id=to_state.entity_id) elif event.event_type == EVENT_HOMEASSISTANT_START: if start_stop_events.get(event.time_fired.minute) == 2: continue yield Entry( event.time_fired, "Home Assistant", "started", domain=HA_DOMAIN) elif event.event_type == EVENT_HOMEASSISTANT_STOP: if start_stop_events.get(event.time_fired.minute) == 2: action = "restarted" else: action = "stopped" yield Entry( event.time_fired, "Home Assistant", action, domain=HA_DOMAIN) elif event.event_type == EVENT_LOGBOOK_ENTRY: domain = event.data.get(ATTR_DOMAIN) entity_id = event.data.get(ATTR_ENTITY_ID) if domain is None and entity_id is not None: try: domain = util.split_entity_id(str(entity_id))[0] except IndexError: pass yield Entry( event.time_fired, event.data.get(ATTR_NAME), event.data.get(ATTR_MESSAGE), domain, entity_id)