def get_targets(self, targets: str, level: str) -> list: """Return list of targets based on given level and targets string.""" targets_split = targets.split(",") targets_list = [] if level == self.NotificationLevel.emergency.value: if "everyone" in targets_split: targets_list.append(HOUSE[NOTIFIER]) for person, attribute in PERSONS.items(): targets_list.append(attribute[NOTIFIER]) else: if "home" in targets_split: targets_list.append(HOUSE[NOTIFIER]) for person, attribute in PERSONS.items(): if person in targets_split: targets_list.append(attribute[NOTIFIER]) else: if "everyone" in targets_split: targets_list.append(HOUSE[NOTIFIER]) for person, attribute in PERSONS.items(): if self.target_available(person): targets_list.append(attribute[NOTIFIER]) else: if "home" in targets_split: targets_list.append(HOUSE[NOTIFIER]) for person, attribute in PERSONS.items(): if person in targets_split and self.target_available( person): targets_list.append(attribute[NOTIFIER]) return targets_list
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"], )
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"], )
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)
def who_in_state(self, *presence_states: Enum) -> list: """Return list of person in given state.""" presence_state_list = [ presence_state.value for presence_state in presence_states ] return [ person for person, attribute in PERSONS.items() if self.get_state(attribute[PRESENCE_STATE]) in presence_state_list ]
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")
def add_item_to_briefing(self, notification: Notification) -> None: """Add given notification to the briefing list.""" for target in notification.targets.split(","): if target in PERSONS.keys(): self.briefing_list[target] = { notification.title: { TITLE: notification.title, MESSAGE: notification.message, DATA: notification.data, } }
def configure(self): """Configure.""" self.briefing_list = {} for person, attribute in PERSONS.items(): self.listen_state( self.someone_arrived, attribute[PRESENCE_STATE], new=self.presence_app.PresenceState.just_arrived.value, person=person, ) self.listen_state(self.sleep_mode_deactivated, MODES[SLEEP_MODE], new=OFF)
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!")
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"], )
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"], )
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"], )
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!")
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")
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.")
def configure(self) -> None: """Configure.""" for person, attribute in PERSONS.items(): # get key topic for mqtt room device tracker keys_topic = attribute.get("keys_topic") # set initial state if self.get_state(attribute[KEYS]) == NOT_HOME: self.select_option(attribute[PRESENCE_STATE], self.PresenceState.away.value) else: self.select_option(attribute[PRESENCE_STATE], self.PresenceState.home.value) # away/extented away to just arrived self.listen_state( self.set_presence_person, attribute[KEYS], old=NOT_HOME, input_select=attribute[PRESENCE_STATE], person=person, key_topic=keys_topic, target_state=self.PresenceState.just_arrived.value, ) # home to just left self.listen_state( self.set_presence_person, attribute[KEYS], new=NOT_HOME, input_select=attribute[PRESENCE_STATE], person=person, keys_topic=keys_topic, target_state=self.PresenceState.just_left.value, ) # just arrived to home self.listen_state( self.set_presence_person, attribute[PRESENCE_STATE], new=self.PresenceState.just_arrived.value, duration=60 * 5, input_select=attribute[PRESENCE_STATE], person=person, keys_topic=keys_topic, target_state=self.PresenceState.home.value, ) # just left to just arrived = home self.listen_state( self.set_presence_person, attribute[PRESENCE_STATE], old=self.PresenceState.just_left.value, new=self.PresenceState.just_arrived.value, input_select=attribute[PRESENCE_STATE], person=person, keys_topic=keys_topic, target_state=self.PresenceState.home.value, ) # just left to away self.listen_state( self.set_presence_person, attribute[PRESENCE_STATE], new=self.PresenceState.just_left.value, duration=60 * 5, input_select=attribute[PRESENCE_STATE], person=person, keys_topic=keys_topic, target_state=self.PresenceState.away.value, ) # away to extended away self.listen_state( self.set_presence_person, attribute[PRESENCE_STATE], new=self.PresenceState.away.value, duration=60 * 60 * 24, input_select=attribute[PRESENCE_STATE], person=person, keys_topic=keys_topic, target_state=self.PresenceState.extended_away.value, ) # listen state to trigger house state change self.listen_state(self.set_presence_house, attribute[PRESENCE_STATE])
def everyone_vacation(self): """Return true if everyone is on vacation.""" return self.who_in_state(self.PresenceState.extended_away) == list( PERSONS.keys())
def everyone_home(self) -> bool: """Return true if everyone is home.""" return self.persons_home == list(PERSONS.keys())
def configure(self) -> None: """Configure.""" for person, attribute in PERSONS.items(): self.listen_state( self.phone_call_changed, attribute[PHONE_CALL_BOOL], person=person )