def start_refresh(*args):
            """Register state tracking."""
            @callback
            def force_refresh(*args):
                """Force the component to refresh."""
                self.async_schedule_update_op_state(True)

            force_refresh()
            async_track_state_change(self.oppelf._entity_id, force_refresh)
 async def async_added_to_opp(self):
     """Register listeners."""
     for entity_id in self._entities:
         new_state = self.opp.states.get(entity_id)
         self.update_supported_features(entity_id,
                                        None,
                                        new_state,
                                        update_state=False)
     async_track_state_change(self.opp, self._entities,
                              self.update_supported_features)
     await self.async_update()
Exemple #3
0
    def __init__(
        self,
        opp,
        entity_id,
        name,
        watched_entity_id,
        state,
        repeat,
        skip_first,
        message_template,
        done_message_template,
        notifiers,
        can_ack,
        title_template,
        data,
    ):
        """Initialize the alert."""
        self.opp = opp
        self._name = name
        self._alert_state = state
        self._skip_first = skip_first
        self._data = data

        self._message_template = message_template
        if self._message_template is not None:
            self._message_template.opp = opp

        self._done_message_template = done_message_template
        if self._done_message_template is not None:
            self._done_message_template.opp = opp

        self._title_template = title_template
        if self._title_template is not None:
            self._title_template.opp = opp

        self._notifiers = notifiers
        self._can_ack = can_ack

        self._delay = [timedelta(minutes=val) for val in repeat]
        self._next_delay = 0

        self._firing = False
        self._ack = False
        self._cancel = None
        self._send_done_message = False
        self.entity_id = ENTITY_ID_FORMAT.format(entity_id)

        event.async_track_state_change(opp, watched_entity_id,
                                       self.watched_entity_change)
async def async_setup(opp, config):
    """Set up the MQTT state feed."""
    conf = config.get(DOMAIN, {})
    base_topic = conf.get(CONF_BASE_TOPIC)
    pub_include = conf.get(CONF_INCLUDE, {})
    pub_exclude = conf.get(CONF_EXCLUDE, {})
    publish_attributes = conf.get(CONF_PUBLISH_ATTRIBUTES)
    publish_timestamps = conf.get(CONF_PUBLISH_TIMESTAMPS)
    publish_filter = generate_filter(
        pub_include.get(CONF_DOMAINS, []),
        pub_include.get(CONF_ENTITIES, []),
        pub_exclude.get(CONF_DOMAINS, []),
        pub_exclude.get(CONF_ENTITIES, []),
    )
    if not base_topic.endswith("/"):
        base_topic = base_topic + "/"

    @callback
    def _state_publisher(entity_id, old_state, new_state):
        if new_state is None:
            return

        if not publish_filter(entity_id):
            return

        payload = new_state.state

        mybase = base_topic + entity_id.replace(".", "/") + "/"
        opp.components.mqtt.async_publish(mybase + "state", payload, 1, True)

        if publish_timestamps:
            if new_state.last_updated:
                opp.components.mqtt.async_publish(
                    mybase + "last_updated",
                    new_state.last_updated.isoformat(), 1, True)
            if new_state.last_changed:
                opp.components.mqtt.async_publish(
                    mybase + "last_changed",
                    new_state.last_changed.isoformat(), 1, True)

        if publish_attributes:
            for key, val in new_state.attributes.items():
                encoded_val = json.dumps(val, cls=JSONEncoder)
                opp.components.mqtt.async_publish(mybase + key, encoded_val, 1,
                                                  True)

    async_track_state_change(opp, MATCH_ALL, _state_publisher)
    return True
def handle_render_template(opp, connection, msg):
    """Handle render_template command.

    Async friendly.
    """
    template = msg["template"]
    template.opp = opp

    variables = msg.get("variables")

    entity_ids = msg.get("entity_ids")
    if entity_ids is None:
        entity_ids = template.extract_entities(variables)

    @callback
    def state_listener(*_):
        connection.send_message(
            messages.event_message(
                msg["id"], {"result": template.async_render(variables)}
            )
        )

    if entity_ids and entity_ids != MATCH_ALL:
        connection.subscriptions[msg["id"]] = async_track_state_change(
            opp, entity_ids, state_listener
        )
    else:
        connection.subscriptions[msg["id"]] = lambda: None

    connection.send_result(msg["id"])
    state_listener()
Exemple #6
0
    def async_start(self):
        """Start tracking members.

        This method must be run in the event loop.
        """
        if self._async_unsub_state_changed is None:
            self._async_unsub_state_changed = async_track_state_change(
                self.opp, self.tracking, self._async_state_changed_listener)
Exemple #7
0
    async def run_handler(self):
        """Handle accessory driver started event.

        Run inside the Open Peer Power event loop.
        """
        state = self.opp.states.get(self.entity_id)
        self.opp.async_add_job(self.update_state_callback, None, None, state)
        async_track_state_change(self.opp, self.entity_id, self.update_state_callback)

        if self.linked_battery_sensor:
            battery_state = self.opp.states.get(self.linked_battery_sensor)
            self.opp.async_add_job(
                self.update_linked_battery, None, None, battery_state
            )
            async_track_state_change(
                self.opp, self.linked_battery_sensor, self.update_linked_battery
            )
Exemple #8
0
async def async_setup(opp, config):
    """Set up the MQTT state feed."""
    conf = config.get(DOMAIN)
    publish_filter = convert_include_exclude_filter(conf)
    base_topic = conf.get(CONF_BASE_TOPIC)
    publish_attributes = conf.get(CONF_PUBLISH_ATTRIBUTES)
    publish_timestamps = conf.get(CONF_PUBLISH_TIMESTAMPS)
    if not base_topic.endswith("/"):
        base_topic = f"{base_topic}/"

    @callback
    def _state_publisher(entity_id, old_state, new_state):
        if new_state is None:
            return

        if not publish_filter(entity_id):
            return

        payload = new_state.state

        mybase = f"{base_topic}{entity_id.replace('.', '/')}/"
        opp.components.mqtt.async_publish(f"{mybase}state", payload, 1, True)

        if publish_timestamps:
            if new_state.last_updated:
                opp.components.mqtt.async_publish(
                    f"{mybase}last_updated", new_state.last_updated.isoformat(), 1, True
                )
            if new_state.last_changed:
                opp.components.mqtt.async_publish(
                    f"{mybase}last_changed", new_state.last_changed.isoformat(), 1, True
                )

        if publish_attributes:
            for key, val in new_state.attributes.items():
                encoded_val = json.dumps(val, cls=JSONEncoder)
                opp.components.mqtt.async_publish(mybase + key, encoded_val, 1, True)

    async_track_state_change(opp, MATCH_ALL, _state_publisher)
    return True
Exemple #9
0
async def test_multiple_emails(opp):
    """Test multiple emails."""
    states = []

    test_message1 = email.message.Message()
    test_message1["From"] = "*****@*****.**"
    test_message1["Subject"] = "Test"
    test_message1["Date"] = datetime.datetime(2016, 1, 1, 12, 44, 57)
    test_message1.set_payload("Test Message")

    test_message2 = email.message.Message()
    test_message2["From"] = "*****@*****.**"
    test_message2["Subject"] = "Test 2"
    test_message2["Date"] = datetime.datetime(2016, 1, 1, 12, 44, 57)
    test_message2.set_payload("Test Message 2")

    def state_changed_listener(entity_id, from_s, to_s):
        states.append(to_s)

    async_track_state_change(opp, ["sensor.emailtest"], state_changed_listener)

    sensor = imap_email_content.EmailContentSensor(
        opp,
        FakeEMailReader(deque([test_message1, test_message2])),
        "test_emails_sensor",
        ["*****@*****.**"],
        None,
    )

    sensor.entity_id = "sensor.emailtest"

    sensor.async_schedule_update_op_state(True)
    await opp.async_block_till_done()
    sensor.async_schedule_update_op_state(True)
    await opp.async_block_till_done()

    assert states[0].state == "Test"
    assert states[1].state == "Test 2"

    assert sensor.extra_state_attributes["body"] == "Test Message 2"
    async def async_added_to_opp(self) -> None:
        """Register callbacks."""

        @callback
        def async_state_changed_listener(
            entity_id: str, old_state: State, new_state: State
        ) -> None:
            """Handle child updates."""
            self.async_schedule_update_op_state(True)

        assert self.opp is not None
        self._async_unsub_state_changed = async_track_state_change(
            self.opp, self._switch_entity_id, async_state_changed_listener
        )
    async def async_update_config(self, config):
        """Handle when the config is updated."""
        self._config = config

        if self._unsub_track_device is not None:
            self._unsub_track_device()
            self._unsub_track_device = None

        trackers = self._config[CONF_DEVICE_TRACKERS]

        if trackers:
            _LOGGER.debug("Subscribe to device trackers for %s",
                          self.entity_id)

            self._unsub_track_device = async_track_state_change(
                self.opp, trackers, self._async_handle_tracker_update)

        self._update_state()
Exemple #12
0
async def async_attach_trigger(opp, config, action, automation_info):
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    zone_entity_id = config.get(CONF_ZONE)
    event = config.get(CONF_EVENT)

    @callback
    def zone_automation_listener(entity, from_s, to_s):
        """Listen for state changes and calls action."""
        if (from_s and not location.has_location(from_s)
                or not location.has_location(to_s)):
            return

        zone_state = opp.states.get(zone_entity_id)
        if from_s:
            from_match = condition.zone(opp, zone_state, from_s)
        else:
            from_match = False
        to_match = condition.zone(opp, zone_state, to_s)

        # pylint: disable=too-many-boolean-expressions
        if (event == EVENT_ENTER and not from_match and to_match
                or event == EVENT_LEAVE and from_match and not to_match):
            opp.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform": "zone",
                            "entity_id": entity,
                            "from_state": from_s,
                            "to_state": to_s,
                            "zone": zone_state,
                            "event": event,
                        }
                    },
                    context=to_s.context,
                ))

    return async_track_state_change(opp, entity_id, zone_automation_listener,
                                    MATCH_ALL, MATCH_ALL)
Exemple #13
0
async def activate_automation(  # noqa: C901
        opp, device_group, light_group, light_profile, disable_turn_off):
    """Activate the automation."""
    logger = logging.getLogger(__name__)
    device_tracker = opp.components.device_tracker
    group = opp.components.group
    light = opp.components.light
    person = opp.components.person

    if device_group is None:
        device_entity_ids = opp.states.async_entity_ids(device_tracker.DOMAIN)
    else:
        device_entity_ids = group.get_entity_ids(device_group,
                                                 device_tracker.DOMAIN)
        device_entity_ids.extend(
            group.get_entity_ids(device_group, person.DOMAIN))

    if not device_entity_ids:
        logger.error("No devices found to track")
        return

    # Get the light IDs from the specified group
    if light_group is None:
        light_ids = opp.states.async_entity_ids(light.DOMAIN)
    else:
        light_ids = group.get_entity_ids(light_group, light.DOMAIN)

    if not light_ids:
        logger.error("No lights found to turn on")
        return

    @callback
    def anyone_home():
        """Test if anyone is home."""
        return any(device_tracker.is_on(dt_id) for dt_id in device_entity_ids)

    @callback
    def any_light_on():
        """Test if any light on."""
        return any(light.is_on(light_id) for light_id in light_ids)

    def calc_time_for_light_when_sunset():
        """Calculate the time when to start fading lights in when sun sets.

        Returns None if no next_setting data available.

        Async friendly.
        """
        next_setting = get_astral_event_next(opp, SUN_EVENT_SUNSET)
        if not next_setting:
            return None
        return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)

    async def async_turn_on_before_sunset(light_id):
        """Turn on lights."""
        if not anyone_home() or light.is_on(light_id):
            return
        await opp.services.async_call(
            DOMAIN_LIGHT,
            SERVICE_TURN_ON,
            {
                ATTR_ENTITY_ID: light_id,
                ATTR_TRANSITION: LIGHT_TRANSITION_TIME.total_seconds(),
                ATTR_PROFILE: light_profile,
            },
        )

    @callback
    def async_turn_on_factory(light_id):
        """Generate turn on callbacks as factory."""
        async def async_turn_on_light(now):
            """Turn on specific light."""
            await async_turn_on_before_sunset(light_id)

        return async_turn_on_light

    # Track every time sun rises so we can schedule a time-based
    # pre-sun set event
    @callback
    def schedule_light_turn_on(now):
        """Turn on all the lights at the moment sun sets.

        We will schedule to have each light start after one another
        and slowly transition in.
        """
        start_point = calc_time_for_light_when_sunset()
        if not start_point:
            return

        for index, light_id in enumerate(light_ids):
            async_track_point_in_utc_time(
                opp,
                async_turn_on_factory(light_id),
                start_point + index * LIGHT_TRANSITION_TIME,
            )

    async_track_point_in_utc_time(
        opp, schedule_light_turn_on,
        get_astral_event_next(opp, SUN_EVENT_SUNRISE))

    # If the sun is already above horizon schedule the time-based pre-sun set
    # event.
    if is_up(opp):
        schedule_light_turn_on(None)

    @callback
    def check_light_on_dev_state_change(entity, old_state, new_state):
        """Handle tracked device state changes."""
        lights_are_on = any_light_on()
        light_needed = not (lights_are_on or is_up(opp))

        # These variables are needed for the elif check
        now = dt_util.utcnow()
        start_point = calc_time_for_light_when_sunset()

        # Do we need lights?
        if light_needed:
            logger.info("Home coming event for %s. Turning lights on", entity)
            opp.async_create_task(
                opp.services.async_call(
                    DOMAIN_LIGHT,
                    SERVICE_TURN_ON,
                    {
                        ATTR_ENTITY_ID: light_ids,
                        ATTR_PROFILE: light_profile
                    },
                ))

        # Are we in the time span were we would turn on the lights
        # if someone would be home?
        # Check this by seeing if current time is later then the point
        # in time when we would start putting the lights on.
        elif start_point and start_point < now < get_astral_event_next(
                opp, SUN_EVENT_SUNSET):

            # Check for every light if it would be on if someone was home
            # when the fading in started and turn it on if so
            for index, light_id in enumerate(light_ids):
                if now > start_point + index * LIGHT_TRANSITION_TIME:
                    opp.async_create_task(
                        opp.services.async_call(DOMAIN_LIGHT, SERVICE_TURN_ON,
                                                {ATTR_ENTITY_ID: light_id}))

                else:
                    # If this light didn't happen to be turned on yet so
                    # will all the following then, break.
                    break

    async_track_state_change(
        opp,
        device_entity_ids,
        check_light_on_dev_state_change,
        STATE_NOT_HOME,
        STATE_HOME,
    )

    if disable_turn_off:
        return

    @callback
    def turn_off_lights_when_all_leave(entity, old_state, new_state):
        """Handle device group state change."""
        # Make sure there is not someone home
        if anyone_home():
            return

        # Check if any light is on
        if not any_light_on():
            return

        logger.info(
            "Everyone has left but there are lights on. Turning them off")
        opp.async_create_task(
            opp.services.async_call(DOMAIN_LIGHT, SERVICE_TURN_OFF,
                                    {ATTR_ENTITY_ID: light_ids}))

    async_track_state_change(
        opp,
        device_entity_ids,
        turn_off_lights_when_all_leave,
        STATE_HOME,
        STATE_NOT_HOME,
    )

    return
async def async_attach_trigger(
    opp: OpenPeerPower,
    config,
    action,
    automation_info,
    *,
    platform_type: str = "state",
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    entity_id = config.get(CONF_ENTITY_ID)
    from_state = config.get(CONF_FROM, MATCH_ALL)
    to_state = config.get(CONF_TO, MATCH_ALL)
    time_delta = config.get(CONF_FOR)
    template.attach(opp, time_delta)
    match_all = from_state == MATCH_ALL and to_state == MATCH_ALL
    unsub_track_same = {}
    period: Dict[str, timedelta] = {}

    @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."""
            opp.async_run_job(
                action(
                    {
                        "trigger": {
                            "platform":
                            platform_type,
                            "entity_id":
                            entity,
                            "from_state":
                            from_s,
                            "to_state":
                            to_s,
                            "for":
                            time_delta if not time_delta else period[entity],
                        }
                    },
                    context=to_s.context,
                ))

        # Ignore changes to state attributes if from/to is in use
        if (not match_all and from_s is not None and to_s is not None
                and from_s.state == to_s.state):
            return

        if not time_delta:
            call_action()
            return

        variables = {
            "trigger": {
                "platform": "state",
                "entity_id": entity,
                "from_state": from_s,
                "to_state": to_s,
            }
        }

        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)
            return

        unsub_track_same[entity] = async_track_same_state(
            opp,
            period[entity],
            call_action,
            lambda _, _2, to_state: to_state.state == to_s.state,
            entity_ids=entity,
        )

    unsub = async_track_state_change(opp, entity_id, state_automation_listener,
                                     from_state, to_state)

    @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
Exemple #15
0
async def async_attach_trigger(opp,
                               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(opp, 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.opp = opp

    @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(opp, 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."""
            opp.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(
                    opp,
                    period[entity],
                    call_action,
                    entity_ids=entity,
                    async_check_same_func=check_numeric_state,
                )
            else:
                call_action()

    unsub = async_track_state_change(opp, 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