def __init__( self, hass, camera_entity, name, category_index, config, ): """Initialize the TensorFlow entity.""" model_config = config.get(CONF_MODEL) self.hass = hass self._camera_entity = camera_entity if name: self._name = name else: self._name = "TensorFlow {}".format( split_entity_id(camera_entity)[1]) self._category_index = category_index self._min_confidence = config.get(CONF_CONFIDENCE) self._file_out = config.get(CONF_FILE_OUT) # handle categories and specific detection areas self._label_id_offset = model_config.get(CONF_LABEL_OFFSET) categories = model_config.get(CONF_CATEGORIES) self._include_categories = [] self._category_areas = {} for category in categories: if isinstance(category, dict): category_name = category.get(CONF_CATEGORY) category_area = category.get(CONF_AREA) self._include_categories.append(category_name) self._category_areas[category_name] = [0, 0, 1, 1] if category_area: self._category_areas[category_name] = [ category_area.get(CONF_TOP), category_area.get(CONF_LEFT), category_area.get(CONF_BOTTOM), category_area.get(CONF_RIGHT), ] else: self._include_categories.append(category) self._category_areas[category] = [0, 0, 1, 1] # Handle global detection area self._area = [0, 0, 1, 1] area_config = model_config.get(CONF_AREA) if area_config: self._area = [ area_config.get(CONF_TOP), area_config.get(CONF_LEFT), area_config.get(CONF_BOTTOM), area_config.get(CONF_RIGHT), ] template.attach(hass, self._file_out) self._matches = {} self._total_matches = 0 self._last_image = None self._process_time = 0
def __init__(self, hass, ip_address, port, username, password, hostname, camera_entity, config, name=None): """Init with the API key and model id.""" super().__init__() self._url_check = f"http://{ip_address}:{port}/{CLASSIFIER}/check" self._url_teach = f"http://{ip_address}:{port}/{CLASSIFIER}/teach" self._username = username self._password = password self._hostname = hostname self.hass = hass self._camera = camera_entity self._file_out = config.get(CONF_FILE_OUT) if name: self._name = name else: camera_name = split_entity_id(camera_entity)[1] self._name = f"{CLASSIFIER} {camera_name}" self._matched = {} template.attach(hass, self._file_out)
def __init__( self, hass: HomeAssistant, sequence: Sequence[Dict[str, Any]], name: str, domain: str, *, # Used in "Running <running_description>" log message running_description: Optional[str] = None, change_listener: Optional[Callable[..., Any]] = None, script_mode: str = DEFAULT_SCRIPT_MODE, max_runs: int = DEFAULT_MAX, max_exceeded: str = DEFAULT_MAX_EXCEEDED, logger: Optional[logging.Logger] = None, log_exceptions: bool = True, top_level: bool = True, variables: Optional[ScriptVariables] = None, ) -> None: """Initialize the script.""" all_scripts = hass.data.get(DATA_SCRIPTS) if not all_scripts: all_scripts = hass.data[DATA_SCRIPTS] = [] hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, partial(_async_stop_scripts_at_shutdown, hass)) self._top_level = top_level if top_level: all_scripts.append({ "instance": self, "started_before_shutdown": not hass.is_stopping }) self._hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self.domain = domain self.running_description = running_description or f"{domain} script" self.change_listener = change_listener self.script_mode = script_mode self._set_logger(logger) self._log_exceptions = log_exceptions self.last_action = None self.last_triggered: Optional[datetime] = None self._runs: List[_ScriptRun] = [] self.max_runs = max_runs self._max_exceeded = max_exceeded if script_mode == SCRIPT_MODE_QUEUED: self._queue_lck = asyncio.Lock() self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {} self._repeat_script: Dict[int, Script] = {} self._choose_data: Dict[int, Dict[str, Any]] = {} self._referenced_entities: Optional[Set[str]] = None self._referenced_devices: Optional[Set[str]] = None self.variables = variables self._variables_dynamic = template.is_complex(variables) if self._variables_dynamic: template.attach(hass, variables)
def __init__(self, hass: HomeAssistant, sequence, name: str = None, change_listener=None) -> None: """Initialize the script.""" self.hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self._change_listener = change_listener self._cur = -1 self.last_action = None self.last_triggered = None self.can_cancel = any( CONF_DELAY in action or CONF_WAIT_TEMPLATE in action for action in self.sequence) self._async_listener = [] self._template_cache = {} self._config_cache = {} self._actions = { ACTION_DELAY: self._async_delay, ACTION_WAIT_TEMPLATE: self._async_wait_template, ACTION_CHECK_CONDITION: self._async_check_condition, ACTION_FIRE_EVENT: self._async_fire_event, ACTION_CALL_SERVICE: self._async_call_service, }
def create_rest_data_from_config(hass, config): """Create RestData from config.""" resource = config.get(CONF_RESOURCE) resource_template = config.get(CONF_RESOURCE_TEMPLATE) method = config.get(CONF_METHOD) payload = config.get(CONF_PAYLOAD) verify_ssl = config.get(CONF_VERIFY_SSL) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) headers = config.get(CONF_HEADERS) params = config.get(CONF_PARAMS) timeout = config.get(CONF_TIMEOUT) if resource_template is not None: resource_template.hass = hass resource = resource_template.async_render(parse_result=False) template.attach(hass, headers) template.attach(hass, params) if username and password: if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: auth = httpx.DigestAuth(username, password) else: auth = (username, password) else: auth = None return RestData(hass, method, resource, auth, headers, params, payload, verify_ssl, timeout)
def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables)) except TemplateError as ex: _LOGGER.error("Error rendering data template for %s: %s", host, ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": _LOGGER.error( "Can only generate events under esphome domain! (%s)", host) return hass.bus.async_fire(service.service, service_data) else: hass.async_create_task( hass.services.async_call(domain, service_name, service_data, blocking=True))
def __init__( self, hass: HomeAssistant, sequence: Sequence[Dict[str, Any]], name: Optional[str] = None, change_listener: Optional[Callable[..., Any]] = None, ) -> None: """Initialize the script.""" self.hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self._change_listener = change_listener self._cur = -1 self._exception_step: Optional[int] = None self.last_action = None self.last_triggered: Optional[datetime] = None self.can_cancel = any( CONF_DELAY in action or CONF_WAIT_TEMPLATE in action for action in self.sequence) self._async_listener: List[CALLBACK_TYPE] = [] self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {} self._actions = { ACTION_DELAY: self._async_delay, ACTION_WAIT_TEMPLATE: self._async_wait_template, ACTION_CHECK_CONDITION: self._async_check_condition, ACTION_FIRE_EVENT: self._async_fire_event, ACTION_CALL_SERVICE: self._async_call_service, ACTION_DEVICE_AUTOMATION: self._async_device_automation, ACTION_ACTIVATE_SCENE: self._async_activate_scene, }
def __init__( self, friendly_name, area, price_type, precision, low_price_cutoff, currency, vat, use_cents, api, ad_template, hass, ) -> None: self._friendly_name = friendly_name or "%s %s %s" % ( DEFAULT_NAME, price_type, area, ) self._area = area self._currency = currency or _REGIONS[area][0] self._price_type = price_type self._precision = precision self._low_price_cutoff = low_price_cutoff self._use_cents = use_cents self._api = api self._ad_template = ad_template self._hass = hass if vat is True: self._vat = _REGIONS[area][2] else: self._vat = 0 # Price by current hour. self._current_price = None # Holds the data for today and morrow. self._data_today = None self._data_tomorrow = None # Values for the day self._average = None self._max = None self._min = None self._off_peak_1 = None self._off_peak_2 = None self._peak = None # Check incase the sensor was setup using config flow. # This blow up if the template isnt valid. if not isinstance(self._ad_template, Template): self._ad_template = cv.template(self._ad_template) attach(self._hass, self._ad_template) # To control the updates. self._last_tick = None self._cbs = []
def __init__( self, hass: HomeAssistant, sequence: Sequence[Dict[str, Any]], name: Optional[str] = None, change_listener: Optional[Callable[..., Any]] = None, script_mode: str = DEFAULT_SCRIPT_MODE, max_runs: int = DEFAULT_MAX, logger: Optional[logging.Logger] = None, log_exceptions: bool = True, top_level: bool = True, ) -> None: """Initialize the script.""" all_scripts = hass.data.get(DATA_SCRIPTS) if not all_scripts: all_scripts = hass.data[DATA_SCRIPTS] = [] hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, partial(_async_stop_scripts_at_shutdown, hass)) self._top_level = top_level if top_level: all_scripts.append({ "instance": self, "started_before_shutdown": not hass.is_stopping }) self._hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self.change_listener = change_listener self.script_mode = script_mode if logger: self._logger = logger else: logger_name = __name__ if name: logger_name = ".".join([logger_name, slugify(name)]) self._logger = logging.getLogger(logger_name) self._log_exceptions = log_exceptions self.last_action = None self.last_triggered: Optional[datetime] = None self._runs: List[_ScriptRun] = [] self.max_runs = max_runs if script_mode == SCRIPT_MODE_QUEUED: self._queue_lck = asyncio.Lock() self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {} self._repeat_script: Dict[int, Script] = {} self._choose_data: Dict[int, List[Tuple[List[Callable[[HomeAssistant, Dict], bool]], Script]]] = {} self._referenced_entities: Optional[Set[str]] = None self._referenced_devices: Optional[Set[str]] = None
async def async_call_from_config(hass, config, blocking=False, variables=None, validate_config=True, context=None): """Call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: _LOGGER.error("Invalid config for calling service: %s", ex) return if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: config[CONF_SERVICE_TEMPLATE].hass = hass domain_service = config[CONF_SERVICE_TEMPLATE].async_render( variables) domain_service = cv.service(domain_service) except TemplateError as ex: if blocking: raise _LOGGER.error('Error rendering service name template: %s', ex) return except vol.Invalid: if blocking: raise _LOGGER.error('Template rendered invalid service: %s', domain_service) return domain, service_name = domain_service.split('.', 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: try: template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE]) service_data.update( template.render_complex(config[CONF_SERVICE_DATA_TEMPLATE], variables)) except TemplateError as ex: _LOGGER.error('Error rendering data template: %s', ex) return if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] await hass.services.async_call(domain, service_name, service_data, blocking=blocking, context=context)
def async_prepare_call_from_config( hass: HomeAssistantType, config: ConfigType, variables: TemplateVarsType = None, validate_config: bool = False, ) -> Tuple[str, str, Dict[str, Any]]: """Prepare to call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: raise HomeAssistantError( f"Invalid config for calling service: {ex}" ) from ex if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: domain_service = config[CONF_SERVICE_TEMPLATE] if isinstance(domain_service, template.Template): try: domain_service.hass = hass domain_service = domain_service.async_render(variables) domain_service = cv.service(domain_service) except TemplateError as ex: raise HomeAssistantError( f"Error rendering service name template: {ex}" ) from ex except vol.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid service: {domain_service}" ) from ex domain, service = domain_service.split(".", 1) service_data = {} if CONF_TARGET in config: service_data.update(config[CONF_TARGET]) for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]: if conf not in config: continue try: template.attach(hass, config[conf]) service_data.update(template.render_complex(config[conf], variables)) except TemplateError as ex: raise HomeAssistantError(f"Error rendering data template: {ex}") from ex if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] return domain, service, service_data
def __init__(self, hass, camera_entity, name, session, detection_graph, category_index, config): """Initialize the TensorFlow entity.""" model_config = config.get(CONF_MODEL) self.hass = hass self._camera_entity = camera_entity if name: self._name = name else: self._name = "TensorFlow {0}".format( split_entity_id(camera_entity)[1]) self._session = session self._graph = detection_graph self._category_index = category_index self._min_confidence = config.get(CONF_CONFIDENCE) self._file_out = config.get(CONF_FILE_OUT) # handle categories and specific detection areas categories = model_config.get(CONF_CATEGORIES) self._include_categories = [] self._category_areas = {} for category in categories: if isinstance(category, dict): category_name = category.get(CONF_CATEGORY) category_area = category.get(CONF_AREA) self._include_categories.append(category_name) self._category_areas[category_name] = [0, 0, 1, 1] if category_area: self._category_areas[category_name] = [ category_area.get(CONF_TOP), category_area.get(CONF_LEFT), category_area.get(CONF_BOTTOM), category_area.get(CONF_RIGHT) ] else: self._include_categories.append(category) self._category_areas[category] = [0, 0, 1, 1] # Handle global detection area self._area = [0, 0, 1, 1] area_config = model_config.get(CONF_AREA) if area_config: self._area = [ area_config.get(CONF_TOP), area_config.get(CONF_LEFT), area_config.get(CONF_BOTTOM), area_config.get(CONF_RIGHT) ] template.attach(hass, self._file_out) self._matches = {} self._total_matches = 0 self._last_image = None
def __init__(self, hass, intents): """Initialize the intent handler.""" self.hass = hass intents = copy.deepcopy(intents) template.attach(hass, intents) for name, intent in intents.items(): if CONF_ACTION in intent: intent[CONF_ACTION] = script.Script( hass, intent[CONF_ACTION], "Snips intent {}".format(name)) self.intents = intents
async def webhook_render_template(hass, config_entry, data): """Handle a render template webhook.""" resp = {} for key, item in data.items(): try: tpl = item[ATTR_TEMPLATE] attach(hass, tpl) resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES)) except TemplateError as ex: resp[key] = {"error": str(ex)} return webhook_response(resp, registration=config_entry.data)
def __init__(self, hass, intents): """Initialize Alexa view.""" super().__init__(hass) intents = copy.deepcopy(intents) template.attach(hass, intents) for name, intent in intents.items(): if CONF_ACTION in intent: intent[CONF_ACTION] = script.Script(hass, intent[CONF_ACTION], "Alexa intent {}".format(name)) self.intents = intents
async def async_setup(hass, config): """Activate Alexa component.""" intents = copy.deepcopy(config[DOMAIN]) template.attach(hass, intents) for intent_type, conf in intents.items(): if CONF_ACTION in conf: conf[CONF_ACTION] = script.Script(hass, conf[CONF_ACTION], f"Intent Script {intent_type}") intent.async_register(hass, ScriptIntentHandler(intent_type, conf)) return True
def async_setup(hass, config): """Activate Alexa component.""" intents = copy.deepcopy(config[DOMAIN]) template.attach(hass, intents) for intent_type, conf in intents.items(): if CONF_ACTION in conf: conf[CONF_ACTION] = script.Script( hass, conf[CONF_ACTION], "Intent Script {}".format(intent_type)) intent.async_register(hass, ScriptIntentHandler(intent_type, conf)) return True
def __init__(self, hass, intents): """Initialize Alexa view.""" super().__init__(hass) intents = copy.deepcopy(intents) template.attach(hass, intents) for name, intent in intents.items(): if CONF_ACTION in intent: intent[CONF_ACTION] = script.Script( hass, intent[CONF_ACTION], "Alexa intent {}".format(name)) self.intents = intents
def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) # type: ignore[no-untyped-call] for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables)) except TemplateError as ex: _LOGGER.error("Error rendering data template for %s: %s", host, ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": _LOGGER.error( "Can only generate events under esphome domain! (%s)", host) return # Call native tag scan if service_name == "tag_scanned" and device_id is not None: # Importing tag via hass.components in case it is overridden # in a custom_components (custom_components.tag) tag = hass.components.tag tag_id = service_data["tag_id"] hass.async_create_task(tag.async_scan_tag(tag_id, device_id)) return hass.bus.async_fire( service.service, { ATTR_DEVICE_ID: device_id, **service_data, }, ) else: hass.async_create_task( hass.services.async_call(domain, service_name, service_data, blocking=True))
def __init__(self, hass: HomeAssistant, sequence, name: str=None, change_listener=None) -> None: """Initialize the script.""" self.hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self._change_listener = change_listener self._cur = -1 self.last_action = None self.can_cancel = any(CONF_DELAY in action for action in self.sequence) self._async_unsub_delay_listener = None self._template_cache = {}
def __init__(self,hass,intents): super().__init__() _LOGGER.debug("DingdongIntentsView init ") self.hass = hass intents = copy.deepcopy(intents) template.attach(hass, intents) for name, intent in intents.items(): if CONF_ACTION in intent: intent[CONF_ACTION] = script.Script(hass,intent[CONF_ACTION],"DingDong {}".format(name)) self.intents = intents
async def async_send_message(self, message: str, **kwargs: Any) -> None: """Send a message to Slack.""" data = kwargs.get(ATTR_DATA) or {} try: DATA_SCHEMA(data) except vol.Invalid as err: _LOGGER.error("Invalid message data: %s", err) data = {} title = kwargs.get(ATTR_TITLE) targets = _async_sanitize_channel_names( kwargs.get(ATTR_TARGET, [self._config[CONF_DEFAULT_CHANNEL]])) # Message Type 1: A text-only message if ATTR_FILE not in data: if ATTR_BLOCKS_TEMPLATE in data: value = cv.template_complex(data[ATTR_BLOCKS_TEMPLATE]) template.attach(self._hass, value) blocks = template.render_complex(value) elif ATTR_BLOCKS in data: blocks = data[ATTR_BLOCKS] else: blocks = None return await self._async_send_text_only_message( targets, message, title, username=data.get(ATTR_USERNAME, self._config.get(ATTR_USERNAME)), icon=data.get(ATTR_ICON, self._config.get(ATTR_ICON)), blocks=blocks, ) # Message Type 2: A message that uploads a remote file if ATTR_URL in data[ATTR_FILE]: return await self._async_send_remote_file_message( data[ATTR_FILE][ATTR_URL], targets, message, title, username=data[ATTR_FILE].get(ATTR_USERNAME), password=data[ATTR_FILE].get(ATTR_PASSWORD), ) # Message Type 3: A message that uploads a local file return await self._async_send_local_file_message( data[ATTR_FILE][ATTR_PATH], targets, message, title)
def __init__(self, hass: HomeAssistant, sequence, name: str = None, change_listener=None) -> None: """Initialize the script.""" self.hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self._change_listener = change_listener self._cur = -1 self.last_action = None self.last_triggered = None self.can_cancel = any(CONF_DELAY in action or CONF_WAIT_TEMPLATE in action for action in self.sequence) self._async_listener = [] self._template_cache = {} self._config_cache = {}
async def async_call_from_config(hass, config, blocking=False, variables=None, validate_config=True, context=None): """Call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: _LOGGER.error("Invalid config for calling service: %s", ex) return if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: config[CONF_SERVICE_TEMPLATE].hass = hass domain_service = config[CONF_SERVICE_TEMPLATE].async_render( variables) domain_service = cv.service(domain_service) except TemplateError as ex: if blocking: raise _LOGGER.error('Error rendering service name template: %s', ex) return except vol.Invalid: if blocking: raise _LOGGER.error('Template rendered invalid service: %s', domain_service) return domain, service_name = domain_service.split('.', 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: try: template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE]) service_data.update(template.render_complex( config[CONF_SERVICE_DATA_TEMPLATE], variables)) except TemplateError as ex: _LOGGER.error('Error rendering data template: %s', ex) return if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] await hass.services.async_call( domain, service_name, service_data, blocking=blocking, context=context)
def __init__(self, hass: HomeAssistant, sequence, name: str = None, change_listener=None) -> None: """Initialize the script.""" self.hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self._change_listener = change_listener self._cur = -1 self.last_action = None self.can_cancel = any(CONF_DELAY in action for action in self.sequence) self._lock = threading.Lock() self._unsub_delay_listener = None self._template_cache = {}
def test_not_mutate_input(self): """Test for immutable input.""" config = cv.SERVICE_SCHEMA( { "service": "test_domain.test_service", "entity_id": "hello.world, sensor.beer", "data": {"hello": 1}, "data_template": {"nested": {"value": "{{ 1 + 1 }}"}}, } ) orig = deepcopy(config) # Only change after call is each template getting hass attached template.attach(self.hass, orig) service.call_from_config(self.hass, config, validate_config=False) assert orig == config
def async_on_service_call(service: 'ServiceCall') -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split('.', 1) service_data = service.data if service.data_template: try: data_template = {key: Template(value) for key, value in service.data_template.items()} template.attach(hass, data_template) service_data.update(template.render_complex( data_template, service.variables)) except TemplateError as ex: _LOGGER.error('Error rendering data template: %s', ex) return hass.async_create_task(hass.services.async_call( domain, service_name, service_data, blocking=True))
def async_prepare_call_from_config(hass, config, variables=None, validate_config=False): """Prepare to call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: raise HomeAssistantError( f"Invalid config for calling service: {ex}") from ex if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: config[CONF_SERVICE_TEMPLATE].hass = hass domain_service = config[CONF_SERVICE_TEMPLATE].async_render( variables) domain_service = cv.service(domain_service) except TemplateError as ex: raise HomeAssistantError( f"Error rendering service name template: {ex}") from ex except vol.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid service: {domain_service}") from ex domain, service = domain_service.split(".", 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: try: template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE]) service_data.update( template.render_complex(config[CONF_SERVICE_DATA_TEMPLATE], variables)) except TemplateError as ex: raise HomeAssistantError( f"Error rendering data template: {ex}") from ex if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] return domain, service, service_data
def __init__( self, hass: HomeAssistant, sequence: Sequence[Dict[str, Any]], name: Optional[str] = None, change_listener: Optional[Callable[..., Any]] = None, script_mode: str = DEFAULT_SCRIPT_MODE, queue_max: int = DEFAULT_QUEUE_MAX, logger: Optional[logging.Logger] = None, log_exceptions: bool = True, ) -> None: """Initialize the script.""" self._hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self.change_listener = change_listener self._script_mode = script_mode if logger: self._logger = logger else: logger_name = __name__ if name: logger_name = ".".join([logger_name, slugify(name)]) self._logger = logging.getLogger(logger_name) self._log_exceptions = log_exceptions self.last_action = None self.last_triggered: Optional[datetime] = None self.can_cancel = not self.is_legacy or any( CONF_DELAY in action or CONF_WAIT_TEMPLATE in action for action in self.sequence) self._runs: List[_ScriptRunBase] = [] if script_mode == SCRIPT_MODE_QUEUE: self._queue_max = queue_max self._queue_len = 0 self._queue_lck = asyncio.Lock() self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {} self._referenced_entities: Optional[Set[str]] = None self._referenced_devices: Optional[Set[str]] = None
def test_not_mutate_input(self): """Test for immutable input.""" config = cv.SERVICE_SCHEMA({ 'service': 'test_domain.test_service', 'entity_id': 'hello.world, sensor.beer', 'data': { 'hello': 1, }, 'data_template': { 'nested': { 'value': '{{ 1 + 1 }}' } } }) orig = deepcopy(config) # Only change after call is each template getting hass attached template.attach(self.hass, orig) service.call_from_config(self.hass, config, validate_config=False) assert orig == config
async def async_call_action_from_config(hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context]) -> None: """Execute a device action.""" webhook_id = webhook_id_from_device_id(hass, config[CONF_DEVICE_ID]) if webhook_id is None: raise InvalidDeviceAutomationConfig( "Unable to resolve webhook ID from the device ID") service_name = get_notify_service(hass, webhook_id) if service_name is None: raise InvalidDeviceAutomationConfig( "Unable to find notify service for webhook ID") service_data = {notify.ATTR_TARGET: webhook_id} # Render it here because we have access to variables here. for key in (notify.ATTR_MESSAGE, notify.ATTR_TITLE, notify.ATTR_DATA): if key not in config: continue value_template = config[key] template.attach(hass, value_template) try: service_data[key] = template.render_complex( value_template, variables) except template.TemplateError as err: raise InvalidDeviceAutomationConfig( f"Error rendering {key}: {err}") from err await hass.services.async_call(notify.DOMAIN, service_name, service_data, blocking=True, context=context)
def __init__( self, hass: HomeAssistant, sequence: Sequence[Dict[str, Any]], name: Optional[str] = None, change_listener: Optional[Callable[..., Any]] = None, if_running: Optional[str] = None, run_mode: Optional[str] = None, logger: Optional[logging.Logger] = None, log_exceptions: bool = True, ) -> None: """Initialize the script.""" self._logger = logger or logging.getLogger(__name__) self._hass = hass self.sequence = sequence template.attach(hass, self.sequence) self.name = name self._change_listener = change_listener self.last_action = None self.last_triggered: Optional[datetime] = None self.can_cancel = any( CONF_DELAY in action or CONF_WAIT_TEMPLATE in action for action in self.sequence) if not if_running and not run_mode: self._if_running = IF_RUNNING_PARALLEL self._run_mode = RUN_MODE_LEGACY elif if_running and run_mode == RUN_MODE_LEGACY: self._raise('Cannot use if_running if run_mode is "legacy"') else: self._if_running = if_running or IF_RUNNING_CHOICES[0] self._run_mode = run_mode or RUN_MODE_CHOICES[0] self._runs: List[_ScriptRunBase] = [] self._log_exceptions = log_exceptions self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {} self._referenced_entities: Optional[Set[str]] = None self._referenced_devices: Optional[Set[str]] = None
async def async_attach_trigger( hass: HomeAssistant, config: ConfigType, action: AutomationActionType, automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_data = automation_info["trigger_data"] topic = config[CONF_TOPIC] wanted_payload = config.get(CONF_PAYLOAD) value_template = config.get(CONF_VALUE_TEMPLATE) encoding = config[CONF_ENCODING] or None qos = config[CONF_QOS] job = HassJob(action) variables = None if automation_info: variables = automation_info.get("variables") template.attach(hass, wanted_payload) if wanted_payload: wanted_payload = wanted_payload.async_render(variables, limited=True, parse_result=False) template.attach(hass, topic) if isinstance(topic, template.Template): topic = topic.async_render(variables, limited=True, parse_result=False) topic = mqtt.util.valid_subscribe_topic(topic) template.attach(hass, value_template) @callback def mqtt_automation_listener(mqttmsg): """Listen for MQTT messages.""" payload = mqttmsg.payload if value_template is not None: payload = value_template.async_render_with_possible_json_value( payload, error_value=None, ) if wanted_payload is None or wanted_payload == payload: data = { **trigger_data, "platform": "mqtt", "topic": mqttmsg.topic, "payload": mqttmsg.payload, "qos": mqttmsg.qos, "description": f"mqtt topic {mqttmsg.topic}", } with suppress(ValueError): data["payload_json"] = json_loads(mqttmsg.payload) hass.async_run_hass_job(job, {"trigger": data}) _LOGGER.debug("Attaching MQTT trigger for topic: '%s', payload: '%s'", topic, wanted_payload) remove = await mqtt.async_subscribe(hass, topic, mqtt_automation_listener, encoding=encoding, qos=qos) return remove
async def handle_webhook(hass: HomeAssistantType, webhook_id: str, request: Request) -> Response: """Handle webhook callback.""" if webhook_id in hass.data[DOMAIN][DATA_DELETED_IDS]: return Response(status=410) headers = {} config_entry = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] registration = config_entry.data try: req_data = await request.json() except ValueError: _LOGGER.warning('Received invalid JSON from mobile_app') return empty_okay_response(status=HTTP_BAD_REQUEST) if (ATTR_WEBHOOK_ENCRYPTED not in req_data and registration[ATTR_SUPPORTS_ENCRYPTION]): _LOGGER.warning("Refusing to accept unencrypted webhook from %s", registration[ATTR_DEVICE_NAME]) return error_response(ERR_ENCRYPTION_REQUIRED, "Encryption required") try: req_data = WEBHOOK_PAYLOAD_SCHEMA(req_data) except vol.Invalid as ex: err = vol.humanize.humanize_error(req_data, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response() webhook_type = req_data[ATTR_WEBHOOK_TYPE] webhook_payload = req_data.get(ATTR_WEBHOOK_DATA, {}) if req_data[ATTR_WEBHOOK_ENCRYPTED]: enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA] webhook_payload = _decrypt_payload(registration[CONF_SECRET], enc_data) if webhook_type not in WEBHOOK_TYPES: _LOGGER.error('Received invalid webhook type: %s', webhook_type) return empty_okay_response() data = webhook_payload _LOGGER.debug("Received webhook payload for type %s: %s", webhook_type, data) if webhook_type in WEBHOOK_SCHEMAS: try: data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload) except vol.Invalid as ex: err = vol.humanize.humanize_error(webhook_payload, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response(headers=headers) context = registration_context(registration) if webhook_type == WEBHOOK_TYPE_CALL_SERVICE: try: await hass.services.async_call(data[ATTR_DOMAIN], data[ATTR_SERVICE], data[ATTR_SERVICE_DATA], blocking=True, context=context) # noqa: E722 pylint: disable=broad-except except (vol.Invalid, ServiceNotFound, Exception) as ex: _LOGGER.error( "Error when calling service during mobile_app " "webhook (device name: %s): %s", registration[ATTR_DEVICE_NAME], ex) raise HTTPBadRequest() return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_FIRE_EVENT: event_type = data[ATTR_EVENT_TYPE] hass.bus.async_fire(event_type, data[ATTR_EVENT_DATA], EventOrigin.remote, context=context) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_RENDER_TEMPLATE: resp = {} for key, item in data.items(): try: tpl = item[ATTR_TEMPLATE] attach(hass, tpl) resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES)) # noqa: E722 pylint: disable=broad-except except TemplateError as ex: resp[key] = {"error": str(ex)} return webhook_response(resp, registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_LOCATION: hass.helpers.dispatcher.async_dispatcher_send( SIGNAL_LOCATION_UPDATE.format(config_entry.entry_id), data) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_REGISTRATION: new_registration = {**registration, **data} device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={(ATTR_DEVICE_ID, registration[ATTR_DEVICE_ID]), (CONF_WEBHOOK_ID, registration[CONF_WEBHOOK_ID])}, manufacturer=new_registration[ATTR_MANUFACTURER], model=new_registration[ATTR_MODEL], name=new_registration[ATTR_DEVICE_NAME], sw_version=new_registration[ATTR_OS_VERSION]) hass.config_entries.async_update_entry(config_entry, data=new_registration) return webhook_response(safe_registration(new_registration), registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_REGISTER_SENSOR: entity_type = data[ATTR_SENSOR_TYPE] unique_id = data[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) return error_response(ERR_SENSOR_DUPLICATE_UNIQUE_ID, "{} {} already exists!".format( entity_type, unique_id), status=409) data[CONF_WEBHOOK_ID] = webhook_id hass.data[DOMAIN][entity_type][unique_store_key] = data try: await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) except HomeAssistantError as ex: _LOGGER.error("Error registering sensor: %s", ex) return empty_okay_response() register_signal = '{}_{}_register'.format(DOMAIN, data[ATTR_SENSOR_TYPE]) async_dispatcher_send(hass, register_signal, data) return webhook_response({'success': True}, registration=registration, status=HTTP_CREATED, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_SENSOR_STATES: resp = {} for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] unique_id = sensor[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key not in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to update non-registered sensor: %s", unique_store_key) err_msg = '{} {} is not registered'.format( entity_type, unique_id) resp[unique_id] = { 'success': False, 'error': { 'code': ERR_SENSOR_NOT_REGISTERED, 'message': err_msg } } continue entry = hass.data[DOMAIN][entity_type][unique_store_key] new_state = {**entry, **sensor} hass.data[DOMAIN][entity_type][unique_store_key] = new_state safe = savable_state(hass) try: await hass.data[DOMAIN][DATA_STORE].async_save(safe) except HomeAssistantError as ex: _LOGGER.error("Error updating mobile_app registration: %s", ex) return empty_okay_response() async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) resp[unique_id] = {'success': True} return webhook_response(resp, registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_GET_ZONES: zones = ( hass.states.get(entity_id) for entity_id in sorted(hass.states.async_entity_ids(ZONE_DOMAIN))) return webhook_response(list(zones), registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_GET_CONFIG: hass_config = hass.config.as_dict() resp = { 'latitude': hass_config['latitude'], 'longitude': hass_config['longitude'], 'elevation': hass_config['elevation'], 'unit_system': hass_config['unit_system'], 'location_name': hass_config['location_name'], 'time_zone': hass_config['time_zone'], 'components': hass_config['components'], 'version': hass_config['version'], 'theme_color': MANIFEST_JSON['theme_color'], } if CONF_CLOUDHOOK_URL in registration: resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL] try: resp[CONF_REMOTE_UI_URL] = async_remote_ui_url(hass) except CloudNotAvailable: pass return webhook_response(resp, registration=registration, headers=headers)
async def async_attach_trigger(hass, config, action, automation_info, *, platform_type="numeric_state") -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) value_template = config.get(CONF_VALUE_TEMPLATE) unsub_track_same = {} entities_triggered = set() period: dict = {} if value_template is not None: value_template.hass = hass @callback def check_numeric_state(entity, from_s, to_s): """Return True if criteria are now met.""" if to_s is None: return False variables = { "trigger": { "platform": "numeric_state", "entity_id": entity, "below": below, "above": above, } } return condition.async_numeric_state(hass, to_s, below, above, value_template, variables) @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" @callback def call_action(): """Call action with right context.""" hass.async_run_job( action( { "trigger": { "platform": platform_type, "entity_id": entity, "below": below, "above": above, "from_state": from_s, "to_state": to_s, "for": time_delta if not time_delta else period[entity], } }, context=to_s.context, )) matching = check_numeric_state(entity, from_s, to_s) if not matching: entities_triggered.discard(entity) elif entity not in entities_triggered: entities_triggered.add(entity) if time_delta: variables = { "trigger": { "platform": "numeric_state", "entity_id": entity, "below": below, "above": above, } } try: if isinstance(time_delta, template.Template): period[entity] = vol.All( cv.time_period, cv.positive_timedelta)( time_delta.async_render(variables)) elif isinstance(time_delta, dict): time_delta_data = {} time_delta_data.update( template.render_complex(time_delta, variables)) period[entity] = vol.All( cv.time_period, cv.positive_timedelta)(time_delta_data) else: period[entity] = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error( "Error rendering '%s' for template: %s", automation_info["name"], ex, ) entities_triggered.discard(entity) return unsub_track_same[entity] = async_track_same_state( hass, period[entity], call_action, entity_ids=entity, async_check_same_func=check_numeric_state, ) else: call_action() unsub = async_track_state_change(hass, entity_id, state_automation_listener) @callback def async_remove(): """Remove state listeners async.""" unsub() for async_remove in unsub_track_same.values(): async_remove() unsub_track_same.clear() return async_remove
def __init__(self, hass, flash_briefings): """Initialize Alexa view.""" super().__init__(hass) self.flash_briefings = copy.deepcopy(flash_briefings) template.attach(hass, self.flash_briefings)
async def handle_webhook(hass: HomeAssistantType, webhook_id: str, request: Request) -> Response: """Handle webhook callback.""" if webhook_id in hass.data[DOMAIN][DATA_DELETED_IDS]: return Response(status=410) headers = {} config_entry = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] registration = config_entry.data try: req_data = await request.json() except ValueError: _LOGGER.warning('Received invalid JSON from mobile_app') return empty_okay_response(status=HTTP_BAD_REQUEST) if (ATTR_WEBHOOK_ENCRYPTED not in req_data and registration[ATTR_SUPPORTS_ENCRYPTION]): _LOGGER.warning("Refusing to accept unencrypted webhook from %s", registration[ATTR_DEVICE_NAME]) return error_response(ERR_ENCRYPTION_REQUIRED, "Encryption required") try: req_data = WEBHOOK_PAYLOAD_SCHEMA(req_data) except vol.Invalid as ex: err = vol.humanize.humanize_error(req_data, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response() webhook_type = req_data[ATTR_WEBHOOK_TYPE] webhook_payload = req_data.get(ATTR_WEBHOOK_DATA, {}) if req_data[ATTR_WEBHOOK_ENCRYPTED]: enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA] webhook_payload = _decrypt_payload(registration[CONF_SECRET], enc_data) if webhook_type not in WEBHOOK_SCHEMAS: _LOGGER.error('Received invalid webhook type: %s', webhook_type) return empty_okay_response() try: data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload) except vol.Invalid as ex: err = vol.humanize.humanize_error(webhook_payload, ex) _LOGGER.error('Received invalid webhook payload: %s', err) return empty_okay_response(headers=headers) context = registration_context(registration) if webhook_type == WEBHOOK_TYPE_CALL_SERVICE: try: await hass.services.async_call(data[ATTR_DOMAIN], data[ATTR_SERVICE], data[ATTR_SERVICE_DATA], blocking=True, context=context) # noqa: E722 pylint: disable=broad-except except (vol.Invalid, ServiceNotFound, Exception) as ex: _LOGGER.error("Error when calling service during mobile_app " "webhook (device name: %s): %s", registration[ATTR_DEVICE_NAME], ex) raise HTTPBadRequest() return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_FIRE_EVENT: event_type = data[ATTR_EVENT_TYPE] hass.bus.async_fire(event_type, data[ATTR_EVENT_DATA], EventOrigin.remote, context=context) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_RENDER_TEMPLATE: resp = {} for key, item in data.items(): try: tpl = item[ATTR_TEMPLATE] attach(hass, tpl) resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES)) # noqa: E722 pylint: disable=broad-except except TemplateError as ex: resp[key] = {"error": str(ex)} return webhook_response(resp, registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_LOCATION: see_payload = { ATTR_DEV_ID: registration[ATTR_DEVICE_ID], ATTR_LOCATION_NAME: data.get(ATTR_LOCATION_NAME), ATTR_GPS: data.get(ATTR_GPS), ATTR_GPS_ACCURACY: data.get(ATTR_GPS_ACCURACY), ATTR_BATTERY: data.get(ATTR_BATTERY), ATTR_ATTRIBUTES: { ATTR_SPEED: data.get(ATTR_SPEED), ATTR_ALTITUDE: data.get(ATTR_ALTITUDE), ATTR_COURSE: data.get(ATTR_COURSE), ATTR_VERTICAL_ACCURACY: data.get(ATTR_VERTICAL_ACCURACY), } } try: await hass.services.async_call(DT_DOMAIN, DT_SEE, see_payload, blocking=True, context=context) # noqa: E722 pylint: disable=broad-except except (vol.Invalid, ServiceNotFound, Exception) as ex: _LOGGER.error("Error when updating location during mobile_app " "webhook (device name: %s): %s", registration[ATTR_DEVICE_NAME], ex) return empty_okay_response(headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_REGISTRATION: new_registration = {**registration, **data} device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={ (ATTR_DEVICE_ID, registration[ATTR_DEVICE_ID]), (CONF_WEBHOOK_ID, registration[CONF_WEBHOOK_ID]) }, manufacturer=new_registration[ATTR_MANUFACTURER], model=new_registration[ATTR_MODEL], name=new_registration[ATTR_DEVICE_NAME], sw_version=new_registration[ATTR_OS_VERSION] ) hass.config_entries.async_update_entry(config_entry, data=new_registration) return webhook_response(safe_registration(new_registration), registration=registration, headers=headers) if webhook_type == WEBHOOK_TYPE_REGISTER_SENSOR: entity_type = data[ATTR_SENSOR_TYPE] unique_id = data[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) return error_response(ERR_SENSOR_DUPLICATE_UNIQUE_ID, "{} {} already exists!".format(entity_type, unique_id), status=409) data[CONF_WEBHOOK_ID] = webhook_id hass.data[DOMAIN][entity_type][unique_store_key] = data try: await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) except HomeAssistantError as ex: _LOGGER.error("Error registering sensor: %s", ex) return empty_okay_response() register_signal = '{}_{}_register'.format(DOMAIN, data[ATTR_SENSOR_TYPE]) async_dispatcher_send(hass, register_signal, data) return webhook_response({"status": "registered"}, registration=registration, status=HTTP_CREATED, headers=headers) if webhook_type == WEBHOOK_TYPE_UPDATE_SENSOR_STATES: resp = {} for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] unique_id = sensor[ATTR_SENSOR_UNIQUE_ID] unique_store_key = "{}_{}".format(webhook_id, unique_id) if unique_store_key not in hass.data[DOMAIN][entity_type]: _LOGGER.error("Refusing to update non-registered sensor: %s", unique_store_key) err_msg = '{} {} is not registered'.format(entity_type, unique_id) resp[unique_id] = { 'success': False, 'error': { 'code': ERR_SENSOR_NOT_REGISTERED, 'message': err_msg } } continue entry = hass.data[DOMAIN][entity_type][unique_store_key] new_state = {**entry, **sensor} hass.data[DOMAIN][entity_type][unique_store_key] = new_state safe = savable_state(hass) try: await hass.data[DOMAIN][DATA_STORE].async_save(safe) except HomeAssistantError as ex: _LOGGER.error("Error updating mobile_app registration: %s", ex) return empty_okay_response() async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) resp[unique_id] = {"status": "okay"} return webhook_response(resp, registration=registration, headers=headers)