Example #1
0
class ToggleAtTime(SwitchBase):
    """Define a feature to take action on certain time."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema({vol.Required(CONF_SWITCH): vol_help.entity_id},
                   extra=vol.ALLOW_EXTRA),
        CONF_PROPERTIES:
        vol.Schema(
            {
                vol.Required(CONF_ACTION_TYPE): vol.In(ACTION_TYPES),
                vol.Required(CONF_STATE): str,
                vol.Required(CONF_SCHEDULE_TIME): str,
                vol.Optional(CONF_PARAMETERS): dict,
            },
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        self.switch = self.entities[CONF_SWITCH]
        self.action_type = self.properties[CONF_ACTION_TYPE]
        self.state = self.properties[CONF_STATE]
        self.parameters = self.properties.get(CONF_PARAMETERS, {})

        self.run_daily(
            self.action_on_schedule,
            self.parse_time(self.properties[CONF_SCHEDULE_TIME]),
            action_type=self.action_type,
            state=self.state,
            action_entity=self.switch,
            parameters=self.parameters,
            constrain_app_enabled=1,
        )
Example #2
0
class LastMotion(AppBase):
    """Define a feature to update a sensor with the
       name of the room where last motion was detected"""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_MOTION_SENSORS):
                vol.Schema([vol.Optional(vol_help.entity_id)])
            },
            extra=vol.ALLOW_EXTRA,
        )
    })

    def configure(self):
        """Configure."""
        for sensor in self.entities[CONF_MOTION_SENSORS]:
            self.listen_state(self.motion_detected, sensor, new="on")

    def motion_detected(self, entity: Union[str, dict], attribute: str,
                        old: str, new: str, kwargs: dict) -> None:
        """Select the room input select based on the triggered entity."""
        room_name = entity.split(".")[1].split("_", 1)[-1].capitalize()
        self.select_option(HOUSE["last_motion"], room_name)
Example #3
0
class NotifyOnHighHumidity(AppBase):
    """Define a feature to notify on high humidity."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_PROPERTIES:
        vol.Schema({vol.Required(CONF_CHECK_INTERVAL): int},
                   extra=vol.ALLOW_EXTRA),
        CONF_NOTIFICATIONS:
        vol.Schema({vol.Required(CONF_TARGETS): str}),
    })

    def configure(self):
        """Configure."""
        self.run_every(
            self.send_notification,
            datetime.datetime.now(),
            self.properties[CONF_CHECK_INTERVAL] * 60,
            constrain_app_enabled=1,
        )

    def send_notification(self, kwargs: dict) -> None:
        """Send notification on high humidity."""
        if self.climate_app.humidity_in_room_high:
            self.notification_app.notify(
                kind=SINGLE,
                level=HOME,
                title="Hohe Luftfeuchtigkeit!",
                message=
                f"Im {', '.join(self.climate_app.which_room_high_humidity)} "
                f"herrscht eine hohe Luftfeuchtigkeit",
                targets=self.notifications[CONF_TARGETS],
            )
Example #4
0
class NotifyOnCleaningDay(AppBase):
    """Define a feature to send a notification in morning of the cleaning day."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_PROPERTIES:
        vol.Schema(
            {vol.Optional(CONF_REMINDER_TIME): vol_help.valid_time},
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {vol.Required(CONF_TARGETS): vol.In(PERSONS.keys())},
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        self.run_daily(
            self.notify_on_cleaning_day,
            self.parse_time(self.properties[CONF_REMINDER_TIME]),
            constrain_app_enabled=1,
        )

    def notify_on_cleaning_day(self, kwargs: dict) -> None:
        """Send notification in the morning to remind of cleaning day."""
        self.notification_app.notify(
            kind="single",
            level="emergency",
            title="Putztag",
            message=f"Heute ist Putztag. Bitte Möbel richten!",
            targets=self.notifications["targets"],
        )
Example #5
0
class NotifyOnBadLoginAttempt(AppBase):
    """Define a feature to send a notification when bad login happened."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_NOTIFICATIONS:
        vol.Schema(
            {vol.Required(CONF_TARGETS): vol.In(PERSONS.keys())},
            extra=vol.ALLOW_EXTRA,
        )
    })

    def configure(self):
        """Configure."""
        self.listen_state(
            self.bad_login_attempt,
            "persistent_notification.http_login",
            new="notifying",
        )

    def bad_login_attempt(self, entity: Union[str, dict], attribute: str,
                          old: str, new: str, kwargs: dict) -> None:
        """Send notification when bad login happened."""
        msg = self.get_state("persistent_notification.http_login",
                             attribute="message")
        self.notification_app.notify(
            kind="single",
            level="emergency",
            title="Falscher Loginversuch",
            message=msg,
            targets=self.notifications["targets"],
        )
Example #6
0
class WasherAutomation(AppBase):  # pylint: disable=too-few-public-methods
    """Define a base for washer-type appliances automations."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_POWER): vol_help.entity_id,
                vol.Required(CONF_STATUS): vol_help.entity_id,
            },
            extra=vol.ALLOW_EXTRA,
        )
    })

    class WasherStates(Enum):
        """Define an enum for Washer states."""

        clean = "Sauber"
        dirty = "Dreckig"
        running = "Läuft"
        drying = "Trocknung"

    @property
    def washer_state(self) -> "WasherStates":
        """Return the current state of the washer appliance."""
        return self.WasherStates(self.get_state(self.entities[CONF_STATUS]))

    @washer_state.setter
    def washer_state(self, washer_state: WasherStates) -> None:
        """Set the the washer appliance to given state."""
        self.select_option(self.entities[CONF_STATUS], washer_state.value)
Example #7
0
class Person(AppBase):
    """Representation of a Person."""

    APP_SCHEMA = APP_SCHEMA.extend({
        vol.Required("person"):
        str,
        vol.Required("attributes"):
        vol.Schema({
            vol.Required("full_name"): str,
            vol.Optional("notifiers"): vol.All(cv.ensure_list, [str]),
        })
    })

    def configure(self) -> None:
        """Configure a person."""
        person = self.args["person"].lower()
        attributes = self.args["attributes"]
        entity_id = f"person.{person}"

        attributes.update({
            "id": person,
            "home": None,
            "non_binary_presence": None,
            "sleep_state": None,
        })

        self.adbase.set_state(entity_id, attributes=attributes)
Example #8
0
class ToggleOnArrival(SwitchBase):
    """Define a feature to take action on arrival of person/everyone."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema({vol.Required(CONF_SWITCH): vol_help.entity_id},
                   extra=vol.ALLOW_EXTRA),
        CONF_PROPERTIES:
        vol.Schema(
            {
                vol.Required(CONF_ACTION_TYPE): vol.In(ACTION_TYPES),
                vol.Required(CONF_STATE): str,
                vol.Optional(CONF_SPECIFIC_PERSON): vol.In(PERSONS.keys()),
                vol.Optional(CONF_PARAMETERS): dict,
            },
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        self.switch = self.entities[CONF_SWITCH]
        self.action_type = self.properties[CONF_ACTION_TYPE]
        self.state = self.properties[CONF_STATE]
        self.delay = self.properties.get(CONF_DELAY)
        self.parameters = self.properties.get(CONF_PARAMETERS, {})
        self.person = self.properties.get(CONF_SPECIFIC_PERSON)

        self.listen_state(self.someone_arrived,
                          HOUSE[CONF_PRESENCE_STATE],
                          constrain_app_enabled=1)

    def someone_arrived(self, entity: Union[str, dict], attribute: str,
                        old: str, new: str, kwargs: dict) -> None:
        """Take action when a person arrives."""
        someone_home_states = [
            self.presence_app.HouseState.someone.value,
            self.presence_app.HouseState.everyone.value,
        ]

        # Only take action if noone was home before
        if (new in someone_home_states) and (old not in someone_home_states):
            persons_home = self.presence_app.persons_home
            # Only take action if specified person arrives alone or no
            # person was specified
            if (self.person in persons_home
                    and len(persons_home) == 1) or not self.person:
                if self.delay:
                    self.run_in(
                        self.action_on_schedule,
                        self.delay * 60,
                        action_type=self.action_type,
                        state=self.state,
                        action_entity=self.switch,
                        parameters=self.parameters,
                    )
                else:
                    self.action(self.action_type, self.state, self.switch,
                                **self.parameters)
Example #9
0
class HousePresence(AppBase):
    """Define a base class for house presence."""

    APP_SCHEMA = APP_SCHEMA.extend({vol.Required("house_id"): str})

    def configure(self) -> None:
        """Configure."""
        house_id = self.args["house_id"]
        self.house_entity_id = f"house.{house_id}"
        persons = self.adbase.get_state("person")

        # Listen for person changing home state
        for person in persons.keys():
            self.adbase.listen_state(self.on_presence_change,
                                     person,
                                     attribute="home")

    def on_presence_change(self, entity: str, attribute: str, old: str,
                           new: str, kwargs: dict) -> None:
        """Respond when person changes presence state."""
        person_id = entity.split(".")[1]
        persons = self.adbase.get_state("person")

        persons_home = self.adbase.get_state(self.house_entity_id,
                                             attribute="persons")
        persons_extended_away = [
            person for person, attributes in persons.items() if
            attributes["attributes"]["non_binary_presence"] == "extended_away"
        ]

        # Add/remove person from the house
        if new and person_id not in persons_home:
            persons_home.append(person_id)
        elif person_id in persons_home:
            persons_home.remove(person_id)

        # Set occupancy of the house
        if not persons_home:
            occupied = False
        else:
            occupied = True

        # Set presence state of the house
        if len(persons.keys()) == len(persons_home):
            presence_state = "everyone_home"
        elif len(persons.keys()) == len(persons_extended_away):
            presence_state = "vacation"
        elif not persons_home:
            presence_state = "nobody_home"
        else:
            presence_state = "someone_home"

        self.adbase.set_state(
            self.house_entity_id,
            presence_state=presence_state,
            occupied=occupied,
            persons=persons_home,
        )
        self.adbase.log(f"House Presence: {presence_state.replace('_',' ')}")
Example #10
0
class ClimateAutomation(AppBase):
    """Define a base for climate automations."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_HUMIDITY_SENSORS):
                vol.Schema({str: vol_help.entity_id}),
                vol.Required(CONF_TEMPERATURE_SENSORS):
                vol.Schema({str: vol_help.entity_id}),
                vol.Required(CONF_WINDOW_SENSORS):
                vol_help.entity_id_list,
            },
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_PROPERTIES:
        vol.Schema({CONF_THRESHOLDS: vol.Schema({str: int})},
                   extra=vol.ALLOW_EXTRA),
    })

    def configure(self):
        """Configure."""
        self.humidity_sensors = self.entities[CONF_HUMIDITY_SENSORS]
        self.temp_sensors = self.entities[CONF_TEMPERATURE_SENSORS]
        self.thresholds = self.properties[CONF_THRESHOLDS]

    @property
    def window_is_open(self) -> bool:
        """Return true if a window/door is open."""
        return len(self.which_window_open) != 0

    @property
    def which_window_open(self) -> list:
        """Return a list of open windows/doors."""
        return [
            window for window in self.entities[CONF_WINDOW_SENSORS]
            if self.get_state(window) == "on"
        ]

    @property
    def humidity_in_room_high(self) -> bool:
        """Return true if humidity in a room is high."""
        return len(self.which_room_high_humidity) != 0

    @property
    def which_room_high_humidity(self) -> list:
        """Return a list of rooms with high humidity."""
        return [
            self.room_name(room)
            for room, sensor in self.humidity_sensors.items()
            if float(self.get_state(sensor)) > float(self.thresholds[room])
        ]

    @staticmethod
    def room_name(room: str) -> str:
        """Return the friendly room name."""
        return room.replace("_", " ").capitalize()
Example #11
0
class NotifyWhenBinFull(AppBase):
    """Define a feature to send a notification when the bin is full."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema({vol.Required(VACUUM): vol_help.entity_id},
                   extra=vol.ALLOW_EXTRA),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {
                vol.Required(CONF_TARGETS): vol.In(PERSONS.keys()),
                vol.Optional(CONF_INTERVAL): int,
            },
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        self.listen_state(
            self.notify_bin_full,
            self.vacuum_app.vacuum,
            attribute=BIN_FULL,
            old=False,
            new=True,
            constrain_app_enabled=1,
        )

        self.listen_state(
            self.bin_emptied,
            self.vacuum_app.vacuum,
            attribute=BIN_FULL,
            old=True,
            new=False,
            constrain_app_enabled=1,
        )

    def notify_bin_full(self, entity: Union[str, dict], attribute: str,
                        old: str, new: str, kwargs: dict) -> None:
        """Send repeating notification that bin should be emptied."""
        self.handles[BIN_FULL] = self.notification_app.notify(
            kind="repeat",
            level="home",
            title="Pedro voll!",
            message="Pedro muss geleert werden",
            targets=self.notifications["targets"],
            interval=self.notifications["interval"] * 60,
        )
        self.log("Abfalleimer voll! Benachrichtige zum Leeren.")

    def bin_emptied(self, entity: Union[str, dict], attribute: str, old: str,
                    new: str, kwargs: dict) -> None:
        """Cancel the notification when bin has been emptied."""
        if BIN_FULL in self.handles:
            self.handles.pop(BIN_FULL)()
            self.log("Abfalleimer geleert! Schalte Benachrichtigung aus")
Example #12
0
class ToggleOnStateChange(SwitchBase):
    """Define a feature to take action when an entity enters a state."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_SWITCH): vol_help.entity_id,
                vol.Required(CONF_TARGET): vol_help.entity_id,
            },
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_PROPERTIES:
        vol.Schema(
            {
                vol.Required(CONF_ACTION_TYPE): vol.In(ACTION_TYPES),
                vol.Required(CONF_TARGET_STATE): str,
                vol.Required(CONF_STATE): str,
                vol.Optional(CONF_DELAY): int,
                vol.Optional(CONF_PARAMETERS): dict,
            },
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        self.switch = self.entities[CONF_SWITCH]
        self.action_type = self.properties[CONF_ACTION_TYPE]
        self.state = self.properties[CONF_STATE]
        self.delay = self.properties.get(CONF_DELAY)
        self.parameters = self.properties.get(CONF_PARAMETERS, {})

        self.listen_state(
            self.state_change,
            self.entities[CONF_TARGET],
            new=self.properties[CONF_TARGET_STATE],
            constrain_app_enabled=1,
        )

    def state_change(self, entity: Union[str, dict], attribute: str, old: str,
                     new: str, kwargs: dict) -> None:
        """Take action when entity enters target state."""
        if self.delay:
            self.run_in(
                self.action_on_schedule,
                self.delay * 60,
                action_type=self.action_type,
                state=self.state,
                action_entity=self.switch,
                parameters=self.parameters,
            )
        else:
            self.action(self.action_type, self.state, self.switch,
                        **self.parameters)
Example #13
0
class ToggleOnDeparture(SwitchBase):
    """Define a feature to take action when everyone leaves."""

    APP_SCHEMA = APP_SCHEMA.extend(
        {
            CONF_ENTITIES: vol.Schema(
                {vol.Required(CONF_SWITCH): vol_help.entity_id}, extra=vol.ALLOW_EXTRA
            ),
            CONF_PROPERTIES: vol.Schema(
                {
                    vol.Required(CONF_ACTION_TYPE): vol.In(ACTION_TYPES),
                    vol.Required(CONF_STATE): str,
                    vol.Optional(CONF_PARAMETERS): dict,
                },
                extra=vol.ALLOW_EXTRA,
            ),
        }
    )

    def configure(self) -> None:
        """Configure."""
        self.switch = self.entities[CONF_SWITCH]
        self.action_type = self.properties[CONF_ACTION_TYPE]
        self.state = self.properties[CONF_STATE]
        self.delay = self.properties.get(CONF_DELAY)
        self.parameters = self.properties.get(CONF_PARAMETERS, {})

        self.listen_state(
            self.everyone_left,
            HOUSE[CONF_PRESENCE_STATE],
            new=self.presence_app.HouseState.noone.value,
            constrain_app_enabled=1,
        )

    def everyone_left(
        self, entity: Union[str, dict], attribute: str, old: str, new: str, kwargs: dict
    ) -> None:
        """Take action when everyone left the house."""
        if self.delay:
            self.run_in(
                self.action_on_schedule,
                self.delay * 60,
                action_type=self.action_type,
                state=self.state,
                action_entity=self.switch,
                parameters=self.parameters,
            )
        else:
            self.action(self.action_type, self.state, self.switch, **self.parameters)
Example #14
0
class RemoteAutomation(AppBase):
    """Define a base feature for remote automations."""

    APP_SCHEMA = APP_SCHEMA.extend(
        {
            CONF_ENTITIES: vol.Schema(
                {vol.Required(CONF_HARMONY_REMOTE): vol_help.entity_id},
                extra=vol.ALLOW_EXTRA,
            ),
            CONF_PROPERTIES: vol.Schema(
                {vol.Required(CONF_ACTIVITIES): dict}, extra=vol.ALLOW_EXTRA
            ),
        }
    )

    def configure(self) -> None:
        """Configure."""
        self.remote = self.entities[CONF_HARMONY_REMOTE]
        self.activities = self.properties[CONF_ACTIVITIES]

    @property
    def current_device_id(self) -> Union[int, None]:
        """Get device id of current activity."""
        try:
            return self.activities[self.current_activity_name.replace(" ", "_").lower()]
        except KeyError:
            return None

    @property
    def current_activity_name(self) -> Union[str, None]:
        """Get the name of the current activity."""
        return self.get_state(self.remote, attribute=CURRENT_ACTIVITY)

    @property
    def remote_is_off(self) -> bool:
        """Return the power state of the remote control."""
        return self.current_device_id == -1

    def send_command(self, command: str, **kwargs: dict) -> None:
        """Send a command to the remote."""
        device_id = kwargs.get(CONF_DEVICE, self.current_device_id)
        self.log(device_id)

        self.call_service(
            "remote/send_command",
            entity_id=self.remote,
            device=device_id,
            command=command,
        )
Example #15
0
class RoomPresence(AppBase):
    """Define a base class for room presence."""

    APP_SCHEMA = APP_SCHEMA.extend({
        vol.Required("sensors"):
        vol.Schema({vol.Optional(str): cv.entity_id})
    })

    def configure(self) -> None:
        """Configure."""
        room_presence_sensors = self.args["sensors"]

        for person, sensor in room_presence_sensors.items():
            # Listen for person changing area
            self.hass.listen_state(self.on_sensor_change,
                                   sensor,
                                   duration=5,
                                   person_id=person)

    def on_sensor_change(self, entity: str, attribute: str, old: str, new: str,
                         kwargs: dict) -> None:
        """Respond when room presence sensor changes state."""
        person_id = kwargs["person_id"]
        person_entity = f"person.{person_id}"
        old_state = self.adbase.get_state(person_entity, attribute="area")
        if new != old_state:
            areas = self.adbase.get_state("area")
            area_entity = f"area.{new}"

            # Remove person from other areas
            for area in areas.keys():
                if area != area_entity:
                    persons = self.adbase.get_state(area, attribute="persons")
                    if person_id in persons:
                        persons.remove(person_id)
                        self.adbase.set_state(area, persons=persons)

            # Add person to new area
            if new != "not_home":
                persons = self.adbase.get_state(area_entity,
                                                attribute="persons")
                if person_id not in persons:
                    persons.append(person_id)
                    self.adbase.set_state(area_entity, persons=persons)

            # Set area for person
            self.adbase.set_state(person_entity, area=new)
            self.adbase.log(f"{person_id.capitalize()} Area: {new}")
Example #16
0
class House(AppBase):
    """Representation of a House."""

    APP_SCHEMA = APP_SCHEMA.extend({
        vol.Required("id"):
        str,
        vol.Optional("attributes"):
        vol.Schema({
            vol.Optional("friendly_name"): str,
        })
    })

    def configure(self) -> None:
        """Configure an area."""
        houses = self.adbase.get_state("house")
        house_id = self.args["id"]
        attributes = self.args.get("attributes")
        entity_id = f"house.{house_id}"

        # Create an entity for the house if it doesn't already exist
        if entity_id not in houses.keys():
            if "friendly_name" not in attributes:
                attributes.update({"friendly_name": house_id.title()})

            attributes.update({
                "id": house_id,
                "persons": [],
                "occupied": None,
                "presence_state": None,
                "sleep_state": None
            })

            self.adbase.set_state(entity_id,
                                  state="idle",
                                  attributes=attributes)

        # Listen for no changes in house state for 30 seconds
        self.adbase.listen_state(self.state_changed, entity_id, duration=30)

    def state_changed(self, entity: str, attribute: dict, old: str, new: str,
                      kwargs: dict) -> None:
        """Respond when house doesn't change state for 30s."""
        # Set area to idle
        self.adbase.set_state(entity, state="idle")
Example #17
0
class NotificationOnChange(AppBase):
    """Define a feature to send a notification when the alarm state changed."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_NOTIFICATIONS:
        vol.Schema(
            {vol.Required(CONF_TARGETS): vol.In(PERSONS.keys())},
            extra=vol.ALLOW_EXTRA,
        )
    })

    def configure(self):
        """Configure."""
        self.listen_state(self.alarm_state_changed,
                          HOUSE[ALARM_STATE],
                          constrain_app_enabled=1)

        self.listen_event(
            self.disarm_on_push_notification,
            "html5_notification.clicked",
            action=WRONG_ALARM,
            constrain_app_enabled=1,
        )

    def alarm_state_changed(self, entity: Union[str, dict], attribute: str,
                            old: str, new: str, kwargs: dict) -> None:
        """Send notification when alarm state changed."""
        self.notification_app.notify(
            kind="single",
            level="emergency",
            title="Alarm Status gewechselt",
            message=f"Der neue Alarm Status ist {new}",
            targets=self.notifications["targets"],
            data={"actions": [{
                "action": WRONG_ALARM,
                "title": "Fehlalarm"
            }]},
        )

    def disarm_on_push_notification(self, event_name: str, data: dict,
                                    kwargs: dict) -> None:
        """Disarm when push notification got clicked."""
        self.security_app.alarm_state = self.security_app.AlarmType.disarmed
        self.log("Fehlalarm, Alarmanlage wird ausgeschaltet!")
Example #18
0
class NotifyOnWindowOpen(AppBase):
    """Define a feature to notify on a window open longer than threshold."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {vol.Required(CONF_WINDOW_SENSORS): vol_help.entity_id_list},
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_PROPERTIES:
        vol.Schema({vol.Required(CONF_WINDOW_OPEN_THRESHOLD): int},
                   extra=vol.ALLOW_EXTRA),
        CONF_NOTIFICATIONS:
        vol.Schema({vol.Required(CONF_TARGETS): str}),
    })

    def configure(self):
        """Configure."""
        self.window_open_threshold = self.properties[
            CONF_WINDOW_OPEN_THRESHOLD]

        for entity in self.entities[CONF_WINDOW_SENSORS]:
            self.listen_state(
                self.send_notification,
                entity,
                new="on",
                duration=self.window_open_threshold * 60,
                constrain_app_enabled=1,
            )

    def send_notification(self, entity: Union[str, dict], attribute: str,
                          old: str, new: str, kwargs: dict) -> None:
        """Send notification if a window is open longer than threshold."""
        self.notification_app.notify(
            kind=SINGLE,
            level=EMERGENCY,
            title="Fenster offen!",
            message=f"Das Fenster im "
            f"{entity.split('.')[1].split('_')[-1].capitalize()} "
            f"ist länger als {self.window_open_threshold} "
            f"Minuten offen.",
            targets=self.notifications["targets"],
        )
Example #19
0
class NotifyOnNewVersion(AppBase):
    """Define an automation to notify when a new version is available."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_AVAILABLE):
                vol.Schema([vol.Optional(vol_help.entity_id)])
            },
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {vol.Required(CONF_TARGETS): vol.In(PERSONS.keys())},
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        for entity in self.entities[CONF_AVAILABLE]:
            self.listen_state(self.version_changed,
                              entity,
                              constrain_app_enabled=1)

    def version_changed(self, entity: Union[str, dict], attribute: str,
                        old: str, new: str, kwargs: dict) -> None:
        """Send notification when new version is available."""
        self.log(old)
        self.log(new)
        if new != old:
            app = entity.split(".")[1].split("_")[0]
            self.notification_app.notify(
                kind="single",
                level="home",
                title=f"Neue Version für {app}!",
                message=f"Die Version {self.get_state(entity)} "
                f"für {app} ist verfügbar.",
                targets=self.notifications["targets"],
            )
Example #20
0
class NotifyOnLowBattery(AppBase):
    """Define an automation to notify on low battery."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_TRACKING_DEVICES):
                vol.Schema([vol.Optional(vol_help.entity_id)])
            },
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {vol.Required(CONF_TARGETS): vol.In(PERSONS.keys())},
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_PROPERTIES:
        vol.Schema({vol.Optional(CONF_BATTERY_LOW_THRESHOLD): int},
                   extra=vol.ALLOW_EXTRA),
    })

    def configure(self) -> None:
        """Configure."""
        for entity in self.entities[CONF_TRACKING_DEVICES]:
            self.listen_state(self.battery_low,
                              entity,
                              constrain_app_enabled=1)

    def battery_low(self, entity: Union[str, dict], attribute: str, old: str,
                    new: str, kwargs: dict) -> None:
        """Send notification when battery is below threshold."""
        if float(new) < float(self.properties[CONF_BATTERY_LOW_THRESHOLD]):
            self.notification_app.notify(
                kind="single",
                level="home",
                title="Batterie niedrig!",
                message=f"Die Batterie für {entity} ist niedrig.",
                targets=self.notifications["targets"],
            )
Example #21
0
class MotionLightAutomation(AppBase):  # pylint: disable=too-many-instance-attributes
    """Define a base feature for motion based lights."""

    APP_SCHEMA = APP_SCHEMA.extend(
        {
            CONF_ENTITIES: vol.Schema(
                {
                    vol.Required(CONF_MOTION_SENSOR): vol_help.entity_id,
                    vol.Optional(CONF_LUX_SENSOR): vol_help.entity_id,
                    vol.Required(CONF_LIGHTS): vol.Schema(
                        {
                            vol.Required(MORNING): vol_help.entity_id_list,
                            vol.Required(DAY): vol_help.entity_id_list,
                            vol.Required(NIGHT): vol_help.entity_id_list,
                        }
                    ),
                },
                extra=vol.ALLOW_EXTRA,
            ),
            CONF_PROPERTIES: vol.Schema(
                {
                    vol.Optional(CONF_LUX_THRESHOLD): int,
                    vol.Optional(CONF_DELAY): int,
                    vol.Required(CONF_DAY_STATE_TIME): vol.Schema(
                        {
                            vol.Required(MORNING): str,
                            vol.Required(DAY): str,
                            vol.Required(NIGHT): str,
                        }
                    ),
                    vol.Optional(CONF_BRIGHTNESS_LEVEL): vol.Schema(
                        {vol.Optional(vol.In(DAY_STATES)): int}
                    ),
                    vol.Optional(CONF_LIGHT_COLOR): vol.Schema(
                        {vol.Optional(vol.In(DAY_STATES)): str}
                    ),
                },
                extra=vol.ALLOW_EXTRA,
            ),
        }
    )

    def configure(self) -> None:
        """Configure."""
        self.motion_sensor = self.entities[CONF_MOTION_SENSOR]
        self.delay = self.properties.get(CONF_DELAY, 5) * 60
        self.no_action_entities = self.entities.get(CONF_NO_ACTION_ENTITIES, "")

        self.day_state_map = self.properties[CONF_DAY_STATE_TIME]
        self.brightness_map = self.properties.get(CONF_BRIGHTNESS_LEVEL, {})
        self.color_map = self.properties.get(CONF_LIGHT_COLOR, {})
        self.lights_map = self.entities[CONF_LIGHTS]

        # creates a list of a split of each element in self.lights_map
        self.all_lights = set()
        for lights in self.lights_map.values():
            for light in lights.split(","):
                self.all_lights.add(light)

        self.room_name = self.motion_sensor.split(".")[1].split("_", 1)[-1].capitalize()

        self.listen_state(
            self.motion, self.motion_sensor, new=ON, constrain_app_enabled=1
        )

    def motion(
        self, entity: Union[str, dict], attribute: str, old: str, new: str, kwargs: dict
    ) -> None:
        """Take action on motion."""
        self.log(f"Bewegung im {self.room_name} erkannt.")
        if not self.no_action_entities_on:
            if self.lights_on:
                self.log("Licht ist bereits an, Timer neustarten.")
                self.turn_off_delayed()
            elif self.lux_high:
                self.log("Lichtstärke ist genug hoch, keine Aktion.")
            else:
                self.turn_light_on()
                self.turn_off_delayed()

    def turn_light_on(self) -> None:
        """Turn lights on based on state of day."""
        for entity in self.lights:
            self.turn_on(
                entity, brightness=self.brightness, color_name=self.light_color
            )

        self.log(
            f"Das Licht im {self.room_name} " f"wurde durch Bewegung eingeschaltet."
        )

    def turn_light_off(self, *args: list) -> None:
        """Turn lights off if none of the no action entities is on."""
        if not self.no_action_entities_on:
            for entity in self.lights_on:
                self.turn_off(entity)
            self.log(
                f"Das Licht im {self.room_name} "
                f"wurde durch den Timer ausgeschaltet."
            )

    def turn_off_delayed(self) -> None:
        """Set timer to turn light off after specified delay."""
        if LIGHT_TIMER in self.handles:
            self.cancel_timer(self.handles[LIGHT_TIMER])
            self.handles.pop(LIGHT_TIMER)
        self.handles[LIGHT_TIMER] = self.run_in(self.turn_light_off, self.delay)
        self.log(
            f"Ein Timer von {round(self.delay / 60)} Minuten "
            f"wurde im {self.room_name} eingeschaltet."
        )

    @property
    def no_action_entities_on(self) -> list:
        """Return list of no action entities that are on"""
        return [
            entity
            for entity in self.no_action_entities.split(",")
            if self.get_state(entity) in ON_STATES
        ]

    @property
    def lights_on(self) -> list:
        """Return list of lights that are on."""
        return [entity for entity in self.all_lights if self.get_state(entity) == ON]

    @property
    def lux_high(self) -> bool:
        """Return true if lux in room is above threshold."""
        if CONF_LUX_SENSOR in self.entities:
            lux_sensor = self.entities[CONF_LUX_SENSOR]
            lux_threshold = self.properties.get(CONF_LUX_THRESHOLD, 100)
            return float(self.get_state(lux_sensor)) > float(lux_threshold)
        return False

    @property
    def day_state(self) -> str:
        """Return the state of the day based on the current time."""
        if self.now_is_between(self.day_state_map[MORNING], self.day_state_map[DAY]):
            return MORNING
        if self.now_is_between(self.day_state_map[DAY], self.day_state_map[NIGHT]):
            return DAY
        return NIGHT

    @property
    def lights(self) -> list:
        """Return list of lights to turn on based on state of day."""
        return [entity for entity in self.lights_map[self.day_state].split(",")]

    @property
    def brightness(self) -> float:
        """Return brightness to set light to based on state of day."""
        brightness_level = self.brightness_map.get(self.day_state, 75)
        return 255 / 100 * int(brightness_level)

    @property
    def light_color(self) -> str:
        """Return light color to set light to based on state of day."""
        return self.color_map.get(self.day_state, "white")
Example #22
0
class NotifyOnDeviceOffline(AppBase):
    """Define an automation to notify on device going offline."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_TRACKING_DEVICES):
                vol.Schema([vol.Optional(vol_help.entity_id)])
            },
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {vol.Required(CONF_TARGETS): vol.In(PERSONS.keys())},
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        for entity in self.entities[CONF_TRACKING_DEVICES]:
            device_type = entity.split(".")[0]
            if device_type == SWITCH:
                self.listen_state(
                    self.device_offline,
                    entity,
                    new=OFF,
                    duration=60 * 5,
                    constrain_app_enabled=1,
                )
            elif device_type == DEVICE_TRACKER:
                self.listen_state(
                    self.device_offline,
                    entity,
                    new=NOT_HOME,
                    duration=60 * 5,
                    constrain_app_enabled=1,
                )
            elif device_type == ZWAVE:
                self.listen_state(
                    self.device_offline,
                    entity,
                    new=UNKNOWN,
                    duration=60 * 5,
                    constrain_app_enabled=1,
                )
                self.listen_state(
                    self.device_offline,
                    entity,
                    new=UNAVAILABLE,
                    duration=60 * 5,
                    constrain_app_enabled=1,
                )
            elif device_type in (SENSOR, BINARY_SENSOR):
                self.listen_state(
                    self.device_offline,
                    entity,
                    new=UNAVAILABLE,
                    duration=60 * 5,
                    constrain_app_enabled=1,
                )

    def device_offline(self, entity: Union[str, dict], attribute: str,
                       old: str, new: str, kwargs: dict) -> None:
        """Send notification when device is offline longer than 5 minutes."""
        self.notification_app.notify(
            kind="single",
            level="emergency",
            title="Gerät offline!",
            message=f"Das folgende Gerät ist offline {entity}",
            targets=self.notifications["targets"],
        )
Example #23
0
class SwitchBase(AppBase):
    """Define a base class for switches."""

    APP_SCHEMA = APP_SCHEMA.extend({
        vol.Required("switch_id"):
        str,
        vol.Optional("lights"):
        cv.entity_ids,
        vol.Optional("custom_button_config"):
        vol.Schema({
            vol.Required(str):
            vol.Schema({
                vol.Required("service"): str,
                vol.Optional("entity_id"): cv.entity_ids,
                vol.Optional("data"): dict,
            })
        }),
    })

    def configure(self) -> None:
        """Configure."""
        switch_id = self.args["switch_id"]
        self.lights = self.args.get("lights")
        self.custom_action_map = self.args.get("custom_button_config", {})
        self.action_map = {}
        self.button_map = {}

        # Check if lights are deconz lights
        self.check_deconz_light()

        # Listen for button presses
        self.hass.listen_event(self.on_button_press,
                               "deconz_event",
                               id=switch_id)

    def on_button_press(self, event_name: str, data: dict,
                        kwargs: dict) -> None:
        """Respond on button press."""
        button_name = self.get_button_name(data["event"])
        action_map = {**self.action_map, **self.custom_action_map}

        # Check if button press is configured
        try:
            service_data = action_map[button_name]
        except KeyError:
            return

        # Execute light services
        if isinstance(service_data, str):
            light_service = getattr(SwitchBase, service_data)
            light_service(self)
            self.adbase.log(
                f"Switch executed {service_data} for {', '.join(self.lights)}")
            return

        # Execute custom button presses
        delay = service_data.get("delay")
        if delay:
            self.adbase.run_in(self.action_on_schedule,
                               delay,
                               service_data=service_data)
        else:
            self.action(service_data)

    def check_deconz_light(self) -> bool:
        """Raise error when configured lights are not deconz lights."""
        if self.lights:
            if any(
                    self.hass.get_state(light, attribute="is_deconz_group") is
                    None for light in self.lights):
                raise ValueError(
                    "Only DeCONZ lights can be used with the light configuration"
                )

    def get_button_name(self, button_code) -> str:
        """Get the human friendly name of the button press."""
        try:
            return self.button_map[button_code]
        except KeyError:
            return None

    def get_light_type(self, light: str) -> str:
        """Get the DeCONZ light type of the given light."""
        if self.hass.get_state(light, attribute="is_deconz_group"):
            return "/action"
        return "/state"

    def action(self, service_data) -> None:
        """Execute service."""
        service = service_data["service"].replace(".", "/")
        entity = service_data.get("entity_id")
        data = service_data.get("data", {})
        self.hass.call_service(service, entity_id=entity, **data)
        self.adbase.log(f"Switch executed {service} for {', '.join(entity)}")

    def action_on_schedule(self, kwargs: dict) -> None:
        """Execute service on specified time."""
        self.action(kwargs["service_data"])

    def light_on(self) -> None:
        """Turn lights on."""
        for light in self.lights:
            self.hass.turn_on(light)

    def light_on_full(self) -> None:
        """Turn on light at full brightness."""
        for light in self.lights:
            self.hass.call_service("light/turn_on",
                                   entity_id=light,
                                   brightness_pct=100)

    def light_off(self) -> None:
        """Turn lights off."""
        for light in self.lights:
            self.hass.turn_off(light)

    def light_toggle(self) -> None:
        """Toggle lights."""
        for light in self.lights:
            self.hass.toggle(light)

    def light_dim_up_step(self) -> None:
        """Increase brightness by 10%."""
        for light in self.lights:
            self.hass.call_service("light/turn_on",
                                   entity_id=light,
                                   brightness_step_pct=10)

    def light_dim_up_hold(self) -> None:
        """Smoothly increase brightness."""
        for light in self.lights:
            self.hass.call_service(
                "deconz/configure",
                field=self.get_light_type(light),
                entity=light,
                data={
                    "bri_inc": 254,
                    "transitiontime": 50
                },
            )

    def light_dim_down_step(self) -> None:
        """Decrease brightness by 10%."""
        for light in self.lights:
            self.hass.call_service("light/turn_on",
                                   entity_id=light,
                                   brightness_step_pct=-10)

    def light_dim_down_hold(self) -> None:
        """Smoothly decrease brightness."""
        for light in self.lights:
            self.hass.call_service(
                "deconz/configure",
                field=self.get_light_type(light),
                entity=light,
                data={
                    "bri_inc": -254,
                    "transitiontime": 50
                },
            )

    def light_stop_dim(self) -> None:
        """Stop dimming."""
        for light in self.lights:
            self.hass.call_service(
                "deconz/configure",
                field=self.get_light_type(light),
                entity=light,
                data={"bri_inc": 0},
            )
Example #24
0
class SecurityAutomation(AppBase):
    """Define a base for security automations."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema(
            {
                vol.Required(CONF_ALARM_LIGHTS):
                vol.Schema([vol.Optional(vol_help.entity_id)]),
                vol.Required(CONF_MOTION_SENSORS):
                vol.Schema([vol.Optional(vol_help.entity_id)]),
                vol.Required(CONF_DOOR_SENSORS):
                vol.Schema([vol.Optional(vol_help.entity_id)]),
            },
            extra=vol.ALLOW_EXTRA,
        )
    })

    class AlarmType(Enum):
        """Define an enum for Alarm types."""

        armed_no_motion = "Scharf ohne Bewegung"
        armed_motion = "Scharf mit Bewegung"
        disarmed = "Ungesichert"
        alert = "Einbrecher"

    def configure(self) -> None:
        """Configure."""
        self.alarm_lights = self.entities[CONF_ALARM_LIGHTS]
        self.motion_sensors = self.entities[CONF_MOTION_SENSORS]
        self.door_sensors = self.entities[CONF_DOOR_SENSORS]
        self.code = self.args["code"]

        for entity in self.motion_sensors:
            self.listen_state(self.motion_triggered,
                              entity,
                              new=ON,
                              constrain_app_enabled=1)

        for entity in self.door_sensors:
            self.listen_state(self.door_opened,
                              entity,
                              new=ON,
                              constrain_app_enabled=1)

    @property
    def alarm_state(self) -> "AlarmType":
        """Return the current state of the security system."""
        return self.AlarmType(self.get_state(HOUSE[ALARM_STATE]))

    @alarm_state.setter
    def alarm_state(self, alarm_state: AlarmType) -> None:
        """Set the the security system to given state."""
        self.select_option(HOUSE[ALARM_STATE], alarm_state.value)
        if alarm_state == self.AlarmType.armed_motion:
            self.call_service("alarm_control_panel/alarm_arm_away",
                              entity_id=HOUSE["alarm_panel"],
                              code=self.code)
        elif alarm_state == self.AlarmType.armed_no_motion:
            self.call_service("alarm_control_panel/alarm_arm_home",
                              entity_id=HOUSE["alarm_panel"],
                              code=self.code)
        elif alarm_state == self.AlarmType.disarmed:
            self.call_service("alarm_control_panel/alarm_disarm",
                              entity_id=HOUSE["alarm_panel"],
                              code=self.code)
        elif alarm_state == self.AlarmType.alert:
            self.call_service("alarm_control_panel/alarm_trigger",
                              entity_id=HOUSE["alarm_panel"],
                              code=self.code)

    def motion_triggered(self, entity: Union[str, dict], attribute: str,
                         old: str, new: str, kwargs: dict) -> None:
        """Take action when motion sensor is triggered based on alarm state."""
        if self.alarm_state == self.AlarmType.armed_motion:
            self.alarm_state = self.AlarmType.alert
            self.log(f"Bewegung im "
                     f"{entity.split('.')[1].split('_')[1].capitalize()}!!!")

    def door_opened(self, entity: Union[str, dict], attribute: str, old: str,
                    new: str, kwargs: dict) -> None:
        """Take action when a door is opened based on alarm state."""
        if self.alarm_state in (
                self.AlarmType.armed_motion,
                self.AlarmType.armed_no_motion,
        ):
            self.alarm_state = self.AlarmType.alert
            self.log(f"{entity.split('.')[1].split('_')[0].capitalize()}"
                     f" im/in der "
                     f"{entity.split('.')[1].split('_')[1].capitalize()}"
                     f" wurde geöffnet!!!")

    # test presence first
    # need a way to cancel flash lights
    # if new == 'on' or new == 'offen':
    #     self.log("Lichter werden jetzt blinken!")
    #     for light in self.alarm_lights:
    #         self.turn_on(light, brightness=255, color_name='white')
    #         self.flash_lights(light)

    def flash_lights(self, light: str) -> None:
        """Flash lights as long as alarm state is alert."""
        self.toggle(light)
        if self.self.alarm_state == self.AlarmType.alert:
            self.run_in(self.flash_lights(light), 1)
Example #25
0
class SceneLights(AppBase):
    """Define a feature to change light based on the current activity."""

    APP_SCHEMA = APP_SCHEMA.extend(
        {
            CONF_ENTITIES: vol.Schema(
                {vol.Required(CONF_LIGHTS): vol_help.entity_id_list},
                extra=vol.ALLOW_EXTRA,
            ),
            CONF_PROPERTIES: vol.Schema(
                {
                    vol.Required(CONF_SCENE_COLOR): str,
                    vol.Required(CONF_SCENE_BRIGHTNESS): str,
                    vol.Optional(CONF_SCENE_COLOR): dict,
                    vol.Optional(CONF_SCENE_BRIGHTNESS): dict,
                },
                extra=vol.ALLOW_EXTRA,
            ),
        }
    )

    def configure(self) -> None:
        """Configure."""
        self.lights = self.entities[CONF_LIGHTS].split(",")
        self.scene_color_map = self.properties[CONF_SCENE_COLOR]
        self.scene_brightness_map = self.properties[CONF_SCENE_BRIGHTNESS]
        self.transition_on = self.properties.get(CONF_TRANSITION_ON, 2)
        self.transition_off = self.properties.get(CONF_TRANSITION_OFF, 60)

        self.listen_state(
            self.scene_changed,
            self.remote_app.remote,
            attribute=CURRENT_ACTIVITY,
            constrain_app_enabled=1,
        )

    def scene_changed(
        self, entity: Union[str, dict], attribute: str, old: str, new: str, kwargs: dict
    ) -> None:
        """Change the light when the acitivity changed."""
        if self.scene_name(new) == POWER_OFF:
            for light in self.lights:
                self.turn_off(light, transition=self.transition_off)
        else:
            for light in self.lights:
                self.turn_on(
                    light,
                    brightness=self.brightness(new),
                    color_name=self.light_color(new),
                    transition=self.transition_on,
                )

    def brightness(self, scene: str) -> float:
        """Get the specified brightness for the given scene."""
        brightness_pct = self.scene_brightness_map.get(self.scene_name(scene), 75)
        return 255 / 100 * int(brightness_pct)

    def light_color(self, scene: str) -> str:
        """Get the specified light color for the given scene."""
        return self.scene_color_map.get(self.scene_name(scene), "white")

    @staticmethod
    def scene_name(scene: str) -> str:
        """Convert the scene name to the correct format."""
        return scene.replace(" ", "_").lower()

    def brighten_lights(self):
        """Brighten lights."""
        for light in self.lights:
            self.turn_on(light, brightness=200, color_name="white", transition=2)

    def dim_lights(self):
        """Dim lights."""
        current_activity = self.remote_app.current_activity_name

        for light in self.lights:
            self.turn_on(
                light,
                brightness=self.brightness(current_activity),
                color_name=self.light_color(current_activity),
                transition=2,
            )
Example #26
0
class ReminderAutomation(AppBase):
    """Define a feature for recurring or one time reminders."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_PROPERTIES:
        vol.Schema(
            {
                vol.Required(CONF_TITLE):
                str,
                vol.Required(CONF_MESSAGE):
                str,
                vol.Required(CONF_REMINDER_DATE):
                vol_help.valid_date,
                vol.Required(CONF_REMINDER_TIME):
                vol_help.valid_time,
                vol.Optional(CONF_REPEAT):
                vol.Schema({
                    vol.Required(CONF_REPEAT_TYPE): vol.In(REPEAT_TYPES),
                    vol.Required(CONF_REPEAT_FREQ): int,
                }),
            },
            extra=vol.ALLOW_EXTRA,
        ),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {
                vol.Required(CONF_TARGETS): vol.In(PERSONS.keys()),
                vol.Optional(CONF_INTERVAL): int,
            },
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        self.reminder_time = self.properties[CONF_REMINDER_TIME]
        self.reminder_date = datetime.strptime(
            self.properties[CONF_REMINDER_DATE], "%d.%m.%Y")
        self.repeat_type = self.properties[CONF_REPEAT][CONF_REPEAT_TYPE]
        self.repeat_freq = self.properties[CONF_REPEAT][CONF_REPEAT_FREQ]

        self.run_daily(
            self.check_reminder_date,
            self.parse_time(self.properties[CONF_REMINDER_TIME]),
            constrain_app_enabled=1,
        )

        self.listen_event(
            self.disable_on_push_notification,
            "html5_notification.clicked",
            action=DONE,
            constrain_app_enabled=1,
        )

    def check_reminder_date(self, kwargs: dict) -> None:
        """Check if today is a reminder day, if yes send reminder."""
        days_to_reminder_date = (datetime.today() - self.reminder_date).days
        divisor = None
        if self.repeat_type == DAYS:
            divisor = self.repeat_freq
        elif self.repeat_type == WEEKS:
            divisor = self.repeat_freq * 7
        elif self.repeat_type == MONTHS:
            divisor = self.repeat_freq * 30

        if days_to_reminder_date % divisor == 0:
            self.send_reminder()
            self.log("Erinnerungstag! Erinnerung gesendet.")

    def send_reminder(self) -> None:
        """Send a repeating actionable push notification as a reminder."""
        self.handles[REMINDER] = self.notification_app.notify(
            kind="repeat",
            level="home",
            title=self.properties[CONF_TITLE],
            message=self.properties[CONF_MESSAGE],
            targets=self.notifications[CONF_TARGETS],
            interval=self.notifications[CONF_INTERVAL] * 60,
            data={"actions": [{
                "action": DONE,
                "title": "Erledigt"
            }]},
        )

    def disable_on_push_notification(self, event_name: str, data: dict,
                                     kwargs: dict) -> None:
        """Disable reminder when 'done' got clicked on push notification."""
        if REMINDER in self.handles:
            self.handles.pop(REMINDER)()
            self.log("Aufgabe erledigt! Schalte Erinnerung aus.")
Example #27
0
class VacuumAutomation(AppBase):
    """Define a feature for scheduled cleaning cycle including
       cancellation when someone arrives home."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_ENTITIES:
        vol.Schema({vol.Required(VACUUM): vol_help.entity_id},
                   extra=vol.ALLOW_EXTRA),
        CONF_PROPERTIES:
        vol.Schema({vol.Optional(CONF_CLEANING_TIME): str},
                   extra=vol.ALLOW_EXTRA),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {vol.Required(CONF_TARGETS): vol.In(PERSONS.keys())},
            extra=vol.ALLOW_EXTRA,
        ),
    })

    class VacuumState(Enum):
        """Define an enum for vacuum states."""

        charging = "Charging"
        running = "Running"
        returning = "User Docking"
        stuck = "Stuck"

    def configure(self) -> None:
        """Configure."""
        self.started_by_app = False
        self.vacuum = self.entities[VACUUM]
        cleaning_time = self.parse_time(
            self.properties.get(CONF_CLEANING_TIME, "11:00:00"))

        # scheduled clean cycle
        self.run_daily(self.start_cleaning,
                       cleaning_time,
                       constrain_app_enabled=1)

        # cycle finished
        self.listen_state(
            self.cleaning_finished,
            self.vacuum,
            attribute=STATUS,
            old=self.VacuumState.returning.value,
            new=self.VacuumState.charging.value,
        )

        # cancel cycle if someone arrives home
        self.listen_state(self.cancel_cleaning, HOUSE[PRESENCE_STATE])

        # notify when vacuum stuck
        self.listen_state(
            self.vacuum_stuck,
            self.vacuum,
            attribute=STATUS,
            new=self.VacuumState.stuck.value,
        )

        # turn on/off cleaning mode when cleaning/finished cleaning
        self.listen_state(self.set_cleaning_mode_input_boolean,
                          self.vacuum,
                          attribute=STATUS)

    def start_cleaning(self, kwargs: dict) -> None:
        """Start the scheduled cleaning cycle."""
        self.call_service("vacuum/start_pause", entity_id=self.vacuum)
        self.started_by_app = True
        self.log("Pedro startet die Reinigung!")

    def cleaning_finished(self, entity: Union[str, dict], attribute: str,
                          old: str, new: str, kwargs: dict) -> None:
        """Deactivate input boolean when cleaning cycle finished."""
        self.started_by_app = False
        self.log("Pedro hat die Reinigung beendet")

    def cancel_cleaning(self, entity: Union[str, dict], attribute: str,
                        old: str, new: str, kwargs: dict) -> None:
        """Cancel the cleaning cycle when someone arrives home."""
        if (not self.presence_app.noone_home and self.started_by_app
            ) and self.vacuum_state == self.VacuumState.running.value:
            self.call_service("vacuum/return_to_base", entity_id=self.vacuum)
            self.started_by_app = False
            self.log("Jemand ist gerade angekommen, beende Reiningung!")

    def vacuum_stuck(self, entity: Union[str, dict], attribute: str, old: str,
                     new: str, kwargs: dict) -> None:
        """Notify that the vacuum is stuck."""
        self.notification_app.notify(
            kind="single",
            level="emergency",
            title="Pedro steckt fest!",
            message="Pedro steckt fest und braucht Hilfe!",
            targets=self.notifications["targets"],
        )
        self.log("Pedro steckt fest, sende Benachrichtigung.")

    def set_cleaning_mode_input_boolean(self, entity: Union[str, dict],
                                        attribute: str, old: str, new: str,
                                        kwargs: dict) -> None:
        """Set the input boolean for the cleaning mode."""
        if "cleaning_mode" in MODES and old != new:
            if new == self.VacuumState.running.value:
                self.turn_on(MODES[CLEANING_MODE])
            elif new in (self.VacuumState.charging.value,
                         self.VacuumState.stuck.value):
                self.turn_off(MODES[CLEANING_MODE])

    @property
    def vacuum_state(self) -> "VacuumState":
        """Return the current state of the vacuum cleaner."""
        return self.get_state(self.vacuum, attribute="status")
Example #28
0
class AreaLighting(AppBase):
    """Define a class for Area Lighting."""

    # pylint: disable=too-many-instance-attributes

    APP_SCHEMA = APP_SCHEMA.extend({
        vol.Required("area"):
        str,
        vol.Required("motion_sensors"):
        cv.entity_ids,
        vol.Optional("delay_off", default=600):
        vol.All(vol.Coerce(int), vol.Range(min=1, max=3600)),
        vol.Optional("lights"):
        cv.entity_ids,
        vol.Optional("lights_ct"):
        cv.entity_ids,
        vol.Optional("lights_rgb"):
        cv.entity_ids,
        vol.Optional("default_brightness", default=80):
        vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
        vol.Optional("lux_sensor"):
        cv.entity_id,
        vol.Optional("lux_threshold", default=100):
        vol.Coerce(int),
        vol.Optional("sleep_lights"):
        cv.entity_ids,
        vol.Optional("sleep_lights_ct"):
        cv.entity_ids,
        vol.Optional("sleep_brightness"):
        vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
        vol.Optional("circadian_sensor"):
        cv.entity_id,
        vol.Optional("min_brightness", default=1):
        vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
        vol.Optional("max_brightness", default=100):
        vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
        vol.Optional("min_colortemp", default=2500):
        vol.All(vol.Coerce(int), vol.Range(min=1000, max=12000)),
        vol.Optional("max_colortemp", default=5500):
        vol.All(vol.Coerce(int), vol.Range(min=1000, max=12000)),
        vol.Optional("transition", default=60):
        vol.All(vol.Coerce(int), vol.Range(min=1, max=3600)),
        vol.Optional("update_interval", default=300):
        vol.All(vol.Coerce(int), vol.Range(min=1, max=3600)),
    })

    def configure(self) -> None:
        """Configure."""
        self.area_id = self.args["area"]
        self.motion_sensors = self.args["motion_sensors"]
        self.delay_off = self.args.get("delay_off")
        self.lights = self.args.get("lights")
        self.lights_ct = self.args.get("lights_ct")
        self.lights_rgb = self.args.get("lights_rgb")
        self.default_brightness = self.args.get("default_brightness")
        self.lux_sensor = self.args.get("lux_sensor")
        self.lux_threshold = self.args.get("lux_threshold")
        self.sleep_lights = self.args.get("sleep_lights")
        self.sleep_lights_ct = self.args.get("sleep_lights_ct")
        self.sleep_brightness = self.args.get("sleep_brightness")
        self.circadian_sensor = self.args.get("circadian_sensor")
        self.min_brightness = self.args.get("min_brightness")
        self.max_brightness = self.args.get("max_brightness")
        self.min_colortemp = self.args.get("min_colortemp")
        self.max_colortemp = self.args.get("max_colortemp")
        self.transition = self.args.get("transition")
        self.update_interval = self.args.get("update_interval")

        # Build area entity and get friendly name
        self.area_entity = f"area.{self.area_id}"
        self.area_name = self.adbase.get_state(self.area_entity,
                                               attribute="friendly_name")

        # Create a list of all lights in the area
        lights = [
            self.lights,
            self.lights_ct,
            self.lights_rgb,
            self.sleep_lights,
            self.sleep_lights_ct,
        ]
        lights = [light for light in lights if light]
        self.all_lights = set(chain(*lights))

        # Listen for motion detected
        for sensor in self.motion_sensors:
            self.hass.listen_state(self.on_motion, sensor, new="on")

        # Listen for changes in light state
        if self.circadian_sensor:
            for light in self.all_lights:
                self.hass.listen_state(self.on_light_change, light)

        # Listen for occupancy changes of area
        self.adbase.listen_state(self.on_occupancy_change,
                                 self.area_entity,
                                 attribute="occupied",
                                 new=False)

    def on_motion(self, entity: str, attribute: str, old: str, new: str,
                  kwargs: dict) -> None:
        """Respond when motion is detected."""
        self.adbase.log(f"Motion detected: {self.area_name}")
        # Turn lights on if not already on
        if not self.lights_on():
            self.turn_lights_on()

        # Set motion state of room to True
        self.set_area_motion(True)

        # Start/Restart timer to turn motion state to False
        self.restart_motion_timer()

    def on_light_change(self, entity: str, attribute: str, old: str, new: str,
                        kwargs: dict) -> None:
        """Respond when light changes state."""
        if new != old:
            if new == "on":
                if "circadian_timer" in self.handles:
                    self.adbase.cancel_timer(self.handles["circadian_timer"])
                    self.handles.pop("circadian_timer")
                self.handles["circadian_timer"] = self.adbase.run_every(
                    self.turn_lights_on,
                    f"now+{self.update_interval}",
                    self.update_interval,
                    transition=self.transition,
                )
            elif new == "off":
                # Set motion to False and cancel any existing timers
                if "motion_timer" in self.handles:
                    self.set_area_motion(False)
                    self.adbase.cancel_timer(self.handles["motion_timer"])
                    self.handles.pop("motion_timer")
                if "circadian_timer" in self.handles:
                    self.adbase.cancel_timer(self.handles["circadian_timer"])
                    self.handles.pop("circadian_timer")

    def on_occupancy_change(self, entity: str, attribute: str, old: str,
                            new: str, kwargs: dict) -> None:
        """Respond when occupancy of area changed to False."""
        for light in self.all_lights:
            self.hass.turn_off(light)

    def turn_lights_on(self, *args: list, **kwargs: dict) -> None:
        """Turn on lights."""
        if not self.lux_above_threshold():
            if self.is_sleep() and self.sleep_brightness:
                lights = self.sleep_lights
                lights_ct = self.sleep_lights_ct
                lights_rgb = []
            else:
                lights = self.lights
                lights_ct = self.lights_ct
                lights_rgb = self.lights_rgb

            brightness_pct = int(self.calc_brightness_pct())
            colortemp = int(self.calc_colortemp(brightness_pct))
            mired = color_temperature_kelvin_to_mired(colortemp)
            rgb = tuple(map(int, color_temperature_to_rgb(colortemp)))

            transition = args[0]["transition"] if args else 1

            if lights:
                for light in lights:
                    self.hass.turn_on(light,
                                      brightness_pct=brightness_pct,
                                      transition=transition)
            if lights_ct:
                for light in lights_ct:
                    self.hass.turn_on(
                        light,
                        brightness_pct=brightness_pct,
                        color_temp=mired,
                        transition=transition,
                    )
            if lights_rgb:
                for light in lights_rgb:
                    self.hass.turn_on(
                        light,
                        brightness_pct=brightness_pct,
                        rgb_color=rgb,
                        transition=transition,
                    )

    def set_area_motion(self, motion: bool) -> None:
        """Set motion of area."""
        self.adbase.set_state(self.area_entity, motion=motion)

    def restart_motion_timer(self) -> None:
        """Set/Reset timer to set occupany of are to False."""
        if "motion_timer" in self.handles:
            self.adbase.cancel_timer(self.handles["motion_timer"])
            self.handles.pop("motion_timer")
        self.handles["motion_timer"] = self.adbase.run_in(
            self.disable_area_motion, self.delay_off)

    def disable_area_motion(self, *args: list) -> None:
        """Set area motion to False."""
        self.set_area_motion(False)

    def calc_brightness_pct(self) -> float:
        """Calculate brightness percentage."""
        if self.is_sleep() and self.sleep_brightness:
            return self.sleep_brightness

        if self.circadian_sensor:
            brightness_pct = self.hass.get_state(self.circadian_sensor)
            if float(brightness_pct) > 0:
                return self.max_brightness

            return ((self.max_brightness - self.min_brightness) * (
                (100 + float(brightness_pct)) / 100)) + self.min_brightness

        return self.default_brightness

    def calc_colortemp(self, brightness_pct: float) -> float:
        """Calculate color temperature based on brightness."""
        if brightness_pct > 0:
            return ((self.max_colortemp - self.min_colortemp) *
                    (brightness_pct / 100)) + self.min_colortemp

        return self.min_colortemp

    def lux_above_threshold(self) -> bool:
        """Return true if lux is above threshold."""
        if self.lux_sensor:
            value = float(self.hass.get_state(self.lux_sensor))
            return value > self.lux_threshold

        return False

    def lights_on(self) -> list:
        """Return lights currently on."""
        return [
            entity for entity in self.all_lights
            if self.hass.get_state(entity) == "on"
        ]

    def is_sleep(self) -> bool:
        """Return true if someone is asleep."""
        sleep_state = self.adbase.get_state(self.area_entity,
                                            attribute="sleep_state")
        return sleep_state != "nobody_in_bed"
Example #29
0
class NotifyWhenWasherDone(AppBase):
    """Define a feature to send a notification when the washer has finished."""

    APP_SCHEMA = APP_SCHEMA.extend({
        CONF_PROPERTIES:
        vol.Schema({vol.Required(vol.In(THRESHOLDS_TYPES)): int},
                   extra=vol.ALLOW_EXTRA),
        CONF_NOTIFICATIONS:
        vol.Schema(
            {
                vol.Required(CONF_TARGETS): vol.In(PERSONS.keys()),
                vol.Required(CONF_MSG): str,
                vol.Optional(CONF_INTERVAL): int,
            },
            extra=vol.ALLOW_EXTRA,
        ),
    })

    def configure(self) -> None:
        """Configure."""
        self.listen_state(
            self.power_changed,
            self.manager.entities[CONF_POWER],
            constrain_app_enabled=1,
        )

        self.listen_state(
            self.status_changed,
            self.manager.entities[CONF_STATUS],
            constrain_app_enabled=1,
        )

        self.listen_event(
            self.cancel_on_push_notification,
            "html5_notification.clicked",
            action=DONE,
            constrain_app_enabled=1,
        )

    def power_changed(self, entity: Union[str, dict], attribute: str, old: str,
                      new: str, kwargs: dict) -> None:
        """Set washer state based on power change."""
        power = float(new)
        thresholds = self.properties[CONF_THRESHOLDS]
        if (self.manager.washer_state != self.manager.WasherStates.running
                and power >= thresholds[CONF_RUNNING_THRESHOLD]):
            self.manager.washer_state = self.manager.WasherStatus.running
            self.log("Washer set to Running")
        elif (self.manager.washer_state == self.manager.WasherStates.running
              and power <= thresholds[CONF_DRYING_THRESHOLD]):
            self.manager.washer_state = self.manager.WasherStatus.drying
            self.log("Washer set to Drying")
        elif (self.manager.washer_state == self.manager.WasherStates.drying
              and power == thresholds[CONF_CLEAN_THRESHOLD]):
            self.manager.washer_state = self.manager.WasherStatus.clean
            self.log("Washer set to Clean")

    def status_changed(self, entity: Union[str, dict], attribute: str,
                       old: str, new: str, kwargs: dict) -> None:
        """Take action based on status change."""
        if new == self.manager.WasherStates.clean.value:
            self.handles[WASHER_CLEAN] = self.notification_app.notify(
                kind="repeat",
                level="home",
                title="Waschgerät fertig!",
                message=self.notifications[CONF_MSG],
                targets=self.notifications[CONF_TARGETS],
                interval=self.notifications[CONF_INTERVAL] * 60,
                data={"actions": [{
                    "action": DONE,
                    "title": "Erledigt"
                }]},
            )
            self.log("Waschgerät fertig! Benachrichtige zum Leeren!")

        elif old == self.manager.WasherStates.clean.value:
            if WASHER_CLEAN in self.handles:
                self.handles.pop(WASHER_CLEAN)()
                self.log("Waschgerät geleert. Schalte Benachrichtigung aus.")

    def cancel_on_push_notification(self, event_name: str, data: dict,
                                    kwargs: dict) -> None:
        """Cancel the notification when push notification got clicked."""
        self.manager.washer_state = self.manager.WasherStates.dirty
        self.log("Waschgerät wurde geleert!")
Example #30
0
class HueDimmerSwitch(SwitchBase):
    """Define a Hue Dimmer Switch automation object."""

    APP_SCHEMA = APP_SCHEMA.extend(
        {
            CONF_ENTITIES: vol.Schema(
                {vol.Required(CONF_SWITCH): str}, extra=vol.ALLOW_EXTRA
            ),
            CONF_PROPERTIES: vol.Schema(
                {
                    vol.Required(CONF_BUTTON_CONFIG): vol.Schema(
                        {
                            vol.Optional(vol.In(BUTTON_PRESSES)): vol.Schema(
                                {
                                    vol.Required(CONF_ACTION_TYPE): vol.In(
                                        ACTION_TYPES
                                    ),
                                    vol.Required(
                                        CONF_ACTION_ENTITY
                                    ): vol_help.entity_id,
                                    vol.Optional(CONF_DELAY): int,
                                    vol.Optional(CONF_ACTION): str,
                                    vol.Optional(CONF_PARAMETERS): dict,
                                },
                                extra=vol.ALLOW_EXTRA,
                            )
                        }
                    )
                },
                extra=vol.ALLOW_EXTRA,
            ),
        }
    )

    def configure(self) -> None:
        """Configure."""
        self.switch = self.entities[CONF_SWITCH]
        self.button_config = self.properties[CONF_BUTTON_CONFIG]
        self.button_map = {
            1002: SHORT_PRESS_ON,
            1003: LONG_PRESS_ON,
            2002: SHORT_PRESS_UP,
            2003: LONG_PRESS_UP,
            3002: SHORT_PRESS_DOWN,
            3003: LONG_PRESS_DOWN,
            4002: SHORT_PRESS_OFF,
            4003: LONG_PRESS_OFF,
        }

        self.listen_event(
            self.button_pressed, DECONZ_EVENT, id=self.switch, constrain_app_enabled=1
        )

    def button_pressed(self, event_name: str, data: dict, kwargs: dict) -> None:
        """Take action when button is pressed on dimmer switch."""
        button_code = data[CONF_EVENT]
        button_name = self.button_name(button_code)

        if button_name in self.button_config:
            button_conf = self.button_config[button_name]
            action_type = button_conf[CONF_ACTION_TYPE]
            action_entity = button_conf[CONF_ACTION_ENTITY]
            action = button_conf.get(CONF_ACTION)
            delay = button_conf.get(CONF_DELAY)
            parameters = button_conf.get(CONF_PARAMETERS, {})

            if action_type == "dim":
                brightness = self.get_state(action_entity, attribute="brightness") or 0
                new_brightness = brightness - 25
                if new_brightness <= 0:
                    self.turn_off(action_entity)
                else:
                    self.turn_on(action_entity, brightness=new_brightness)
            elif action_type == "brighten":
                brightness = self.get_state(action_entity, attribute="brightness") or 0
                new_brightness = brightness + 25
                if new_brightness > 255:
                    new_brightness = 255
                self.turn_on(action_entity, brightness=new_brightness)
            else:
                if delay:
                    self.run_in(
                        self.action_on_schedule,
                        delay * 60,
                        action_type=action_type,
                        state=action,
                        action_entity=action_entity,
                        parameters=parameters,
                    )
                else:
                    self.action(action_type, action, action_entity, **parameters)

    def button_name(self, button_code: int) -> Union[str, None]:
        """Return the button name for the provided code."""
        try:
            return self.button_map[button_code]
        except KeyError:
            return None