Beispiel #1
0
    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
Beispiel #2
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)
Beispiel #3
0
    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)
Beispiel #4
0
 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,
     }
Beispiel #5
0
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)
Beispiel #6
0
    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))
Beispiel #7
0
 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,
     }
Beispiel #8
0
    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 = []
Beispiel #9
0
    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
Beispiel #10
0
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)
Beispiel #11
0
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
Beispiel #12
0
    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
Beispiel #13
0
    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
Beispiel #14
0
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)
Beispiel #15
0
    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
Beispiel #16
0
    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
Beispiel #17
0
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
Beispiel #19
0
    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))
Beispiel #21
0
 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
Beispiel #23
0
    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)
Beispiel #24
0
 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 = {}
Beispiel #25
0
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)
Beispiel #26
0
 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
Beispiel #28
0
    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))
Beispiel #29
0
    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))
Beispiel #30
0
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
Beispiel #31
0
    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
Beispiel #33
0
    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)
Beispiel #35
0
 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
Beispiel #36
0
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
Beispiel #37
0
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)
Beispiel #38
0
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
Beispiel #39
0
 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)
Beispiel #40
0
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)