示例#1
0
async def test_update(hass, hass_ws_client, storage_setup):
    """Test updating timer entity."""

    assert await storage_setup()

    timer_id = "from_storage"
    timer_entity_id = f"{DOMAIN}.{DOMAIN}_{timer_id}"
    ent_reg = await entity_registry.async_get_registry(hass)

    state = hass.states.get(timer_entity_id)
    assert state.attributes[ATTR_FRIENDLY_NAME] == "timer from storage"
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN,
                                       timer_id) == timer_entity_id

    client = await hass_ws_client(hass)

    await client.send_json({
        "id": 6,
        "type": f"{DOMAIN}/update",
        f"{DOMAIN}_id": f"{timer_id}",
        CONF_DURATION: 33,
    })
    resp = await client.receive_json()
    assert resp["success"]

    state = hass.states.get(timer_entity_id)
    assert state.attributes[ATTR_DURATION] == str(cv.time_period(33))
示例#2
0
async def test_ws_create(hass, hass_ws_client, storage_setup):
    """Test create WS."""
    assert await storage_setup(items=[])

    timer_id = "new_timer"
    timer_entity_id = f"{DOMAIN}.{timer_id}"
    ent_reg = await entity_registry.async_get_registry(hass)

    state = hass.states.get(timer_entity_id)
    assert state is None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, timer_id) is None

    client = await hass_ws_client(hass)

    await client.send_json({
        "id": 6,
        "type": f"{DOMAIN}/create",
        CONF_NAME: "New Timer",
        CONF_DURATION: 42,
    })
    resp = await client.receive_json()
    assert resp["success"]

    state = hass.states.get(timer_entity_id)
    assert state.state == STATUS_IDLE
    assert state.attributes[ATTR_DURATION] == str(cv.time_period(42))
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN,
                                       timer_id) == timer_entity_id
示例#3
0
    async def _async_delay(self, action, variables, context):
        """Handle delay."""
        # Call ourselves in the future to continue work
        unsub = None

        @callback
        def async_script_delay(now):
            """Handle delay."""
            with suppress(ValueError):
                self._async_listener.remove(unsub)

            self.hass.async_create_task(self.async_run(variables, context))

        delay = action[CONF_DELAY]

        try:
            if isinstance(delay, template.Template):
                delay = vol.All(cv.time_period, cv.positive_timedelta)(
                    delay.async_render(variables))
            elif isinstance(delay, dict):
                delay_data = {}
                delay_data.update(template.render_complex(delay, variables))
                delay = cv.time_period(delay_data)
        except (exceptions.TemplateError, vol.Invalid) as ex:
            _LOGGER.error("Error rendering '%s' delay template: %s", self.name,
                          ex)
            raise _StopScript

        self.last_action = action.get(CONF_ALIAS, "delay {}".format(delay))
        self._log("Executing step %s" % self.last_action)

        unsub = async_track_point_in_utc_time(self.hass, async_script_delay,
                                              date_util.utcnow() + delay)
        self._async_listener.append(unsub)
        raise _SuspendScript
示例#4
0
async def test_config_options(hass):
    """Test configuration options."""
    count_start = len(hass.states.async_entity_ids())

    _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids())

    config = {
        DOMAIN: {
            "test_1": {},
            "test_2": {
                CONF_NAME: "Hello World",
                CONF_ICON: "mdi:work",
                CONF_DURATION: 10,
            },
            "test_3": None,
        }
    }

    assert await async_setup_component(hass, "timer", config)
    await hass.async_block_till_done()

    assert count_start + 3 == len(hass.states.async_entity_ids())
    await hass.async_block_till_done()

    state_1 = hass.states.get("timer.test_1")
    state_2 = hass.states.get("timer.test_2")
    state_3 = hass.states.get("timer.test_3")

    assert state_1 is not None
    assert state_2 is not None
    assert state_3 is not None

    assert state_1.state == STATUS_IDLE
    assert ATTR_ICON not in state_1.attributes
    assert ATTR_FRIENDLY_NAME not in state_1.attributes

    assert state_2.state == STATUS_IDLE
    assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World"
    assert state_2.attributes.get(ATTR_ICON) == "mdi:work"
    assert state_2.attributes.get(ATTR_DURATION) == "0:00:10"

    assert state_3.state == STATUS_IDLE
    assert str(cv.time_period(DEFAULT_DURATION)) == state_3.attributes.get(
        CONF_DURATION
    )
示例#5
0
class Timer(RestoreEntity):
    """Representation of a timer."""

    def __init__(self, config: dict) -> None:
        """Initialize a timer."""
        self._config: dict = config
        self.editable: bool = True
        self._state: str = STATUS_IDLE
        self._duration = cv.time_period_str(config[CONF_DURATION])
        self._remaining: timedelta | None = None
        self._end: datetime | None = None
        self._listener: Callable[[], None] | None = None
        self._restore: bool = self._config.get(CONF_RESTORE, DEFAULT_RESTORE)

        self._attr_should_poll = False
        self._attr_force_update = True

    @classmethod
    def from_yaml(cls, config: dict) -> Timer:
        """Return entity instance initialized from yaml storage."""
        timer = cls(config)
        timer.entity_id = ENTITY_ID_FORMAT.format(config[CONF_ID])
        timer.editable = False
        return timer

    @property
    def name(self):
        """Return name of the timer."""
        return self._config.get(CONF_NAME)

    @property
    def icon(self):
        """Return the icon to be used for this entity."""
        return self._config.get(CONF_ICON)

    @property
    def state(self):
        """Return the current value of the timer."""
        return self._state

    @property
    def extra_state_attributes(self):
        """Return the state attributes."""
        attrs = {
            ATTR_DURATION: _format_timedelta(self._duration),
            ATTR_EDITABLE: self.editable,
        }
        if self._end is not None:
            attrs[ATTR_FINISHES_AT] = self._end.isoformat()
        if self._remaining is not None:
            attrs[ATTR_REMAINING] = _format_timedelta(self._remaining)
        if self._restore:
            attrs[ATTR_RESTORE] = self._restore

        return attrs

    @property
    def unique_id(self) -> str | None:
        """Return unique id for the entity."""
        return self._config[CONF_ID]

    async def async_added_to_hass(self):
        """Call when entity is about to be added to Home Assistant."""
        # If we don't need to restore a previous state or no previous state exists,
        # start at idle
        if not self._restore or (state := await self.async_get_last_state()) is None:
            self._state = STATUS_IDLE
            return

        # Begin restoring state
        self._state = state.state
        self._duration = cv.time_period(state.attributes[ATTR_DURATION])

        # Nothing more to do if the timer is idle
        if self._state == STATUS_IDLE:
            return

        # If the timer was paused, we restore the remaining time
        if self._state == STATUS_PAUSED:
            self._remaining = cv.time_period(state.attributes[ATTR_REMAINING])
            return
        # If we get here, the timer must have been active so we need to decide what
        # to do based on end time and the current time
        end = cv.datetime(state.attributes[ATTR_FINISHES_AT])
        # If there is time remaining in the timer, restore the remaining time then
        # start the timer
        if (remaining := end - dt_util.utcnow().replace(microsecond=0)) > timedelta(0):
            self._remaining = remaining
            self._state = STATUS_PAUSED
            self.async_start()
示例#6
0
    async def async_run(self,
                        variables: Optional[Sequence] = None,
                        context: Optional[Context] = None) -> None:
        """Run script.

        This method is a coroutine.
        """
        self.last_triggered = date_util.utcnow()
        if self._cur == -1:
            self._log('Running script')
            self._cur = 0

        # Unregister callback if we were in a delay or wait but turn on is
        # called again. In that case we just continue execution.
        self._async_remove_listener()

        for cur, action in islice(enumerate(self.sequence), self._cur, None):

            if CONF_DELAY in action:
                # Call ourselves in the future to continue work
                unsub = None

                @callback
                def async_script_delay(now):
                    """Handle delay."""
                    # pylint: disable=cell-var-from-loop
                    self._async_listener.remove(unsub)
                    self.hass.async_create_task(
                        self.async_run(variables, context))

                delay = action[CONF_DELAY]

                try:
                    if isinstance(delay, template.Template):
                        delay = vol.All(cv.time_period, cv.positive_timedelta)(
                            delay.async_render(variables))
                    elif isinstance(delay, dict):
                        delay_data = {}
                        delay_data.update(
                            template.render_complex(delay, variables))
                        delay = cv.time_period(delay_data)
                except (TemplateError, vol.Invalid) as ex:
                    _LOGGER.error("Error rendering '%s' delay template: %s",
                                  self.name, ex)
                    break

                unsub = async_track_point_in_utc_time(
                    self.hass, async_script_delay,
                    date_util.utcnow() + delay)
                self._async_listener.append(unsub)

                self._cur = cur + 1
                if self._change_listener:
                    self.hass.async_add_job(self._change_listener)
                return

            if CONF_WAIT_TEMPLATE in action:
                # Call ourselves in the future to continue work
                wait_template = action[CONF_WAIT_TEMPLATE]
                wait_template.hass = self.hass

                # check if condition already okay
                if condition.async_template(self.hass, wait_template,
                                            variables):
                    continue

                @callback
                def async_script_wait(entity_id, from_s, to_s):
                    """Handle script after template condition is true."""
                    self._async_remove_listener()
                    self.hass.async_create_task(
                        self.async_run(variables, context))

                self._async_listener.append(
                    async_track_template(self.hass, wait_template,
                                         async_script_wait, variables))

                self._cur = cur + 1
                if self._change_listener:
                    self.hass.async_add_job(self._change_listener)

                if CONF_TIMEOUT in action:
                    self._async_set_timeout(action, variables, context,
                                            action.get(CONF_CONTINUE, True))

                return

            if CONF_CONDITION in action:
                if not self._async_check_condition(action, variables):
                    break

            elif CONF_EVENT in action:
                self._async_fire_event(action, variables, context)

            else:
                await self._async_call_service(action, variables, context)

        self._cur = -1
        self.last_action = None
        if self._change_listener:
            self.hass.async_add_job(self._change_listener)
示例#7
0
    async def async_run(self, variables: Optional[Sequence] = None,
                        context: Optional[Context] = None) -> None:
        """Run script.

        This method is a coroutine.
        """
        self.last_triggered = date_util.utcnow()
        if self._cur == -1:
            self._log('Running script')
            self._cur = 0

        # Unregister callback if we were in a delay or wait but turn on is
        # called again. In that case we just continue execution.
        self._async_remove_listener()

        for cur, action in islice(enumerate(self.sequence), self._cur, None):

            if CONF_DELAY in action:
                # Call ourselves in the future to continue work
                unsub = None

                @callback
                def async_script_delay(now):
                    """Handle delay."""
                    # pylint: disable=cell-var-from-loop
                    with suppress(ValueError):
                        self._async_listener.remove(unsub)

                    self.hass.async_create_task(
                        self.async_run(variables, context))

                delay = action[CONF_DELAY]

                try:
                    if isinstance(delay, template.Template):
                        delay = vol.All(
                            cv.time_period,
                            cv.positive_timedelta)(
                                delay.async_render(variables))
                    elif isinstance(delay, dict):
                        delay_data = {}
                        delay_data.update(
                            template.render_complex(delay, variables))
                        delay = cv.time_period(delay_data)
                except (TemplateError, vol.Invalid) as ex:
                    _LOGGER.error("Error rendering '%s' delay template: %s",
                                  self.name, ex)
                    break

                unsub = async_track_point_in_utc_time(
                    self.hass, async_script_delay,
                    date_util.utcnow() + delay
                )
                self._async_listener.append(unsub)

                self._cur = cur + 1
                if self._change_listener:
                    self.hass.async_add_job(self._change_listener)
                return

            if CONF_WAIT_TEMPLATE in action:
                # Call ourselves in the future to continue work
                wait_template = action[CONF_WAIT_TEMPLATE]
                wait_template.hass = self.hass

                # check if condition already okay
                if condition.async_template(
                        self.hass, wait_template, variables):
                    continue

                @callback
                def async_script_wait(entity_id, from_s, to_s):
                    """Handle script after template condition is true."""
                    self._async_remove_listener()
                    self.hass.async_create_task(
                        self.async_run(variables, context))

                self._async_listener.append(async_track_template(
                    self.hass, wait_template, async_script_wait, variables))

                self._cur = cur + 1
                if self._change_listener:
                    self.hass.async_add_job(self._change_listener)

                if CONF_TIMEOUT in action:
                    self._async_set_timeout(
                        action, variables, context,
                        action.get(CONF_CONTINUE, True))

                return

            if CONF_CONDITION in action:
                if not self._async_check_condition(action, variables):
                    break

            elif CONF_EVENT in action:
                self._async_fire_event(action, variables, context)

            else:
                await self._async_call_service(action, variables, context)

        self._cur = -1
        self.last_action = None
        if self._change_listener:
            self.hass.async_add_job(self._change_listener)