├── __init__.py ├── manifest.json ├── README.md └── notify.py /__init__.py: -------------------------------------------------------------------------------- 1 | """IqNotify platform.""" -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "iq_notify", 3 | "name": "IqNotify", 4 | "version": "1.0.0", 5 | "requirements": [], 6 | "dependencies": [], 7 | "codeowners": [] 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iq_notify 2 | 3 | ## How to install 4 | 5 | 1. Clone repository into $HASS_HOME/custom_components/iq_notify. 6 | 2. Restart Home Assistant. 7 | 3. Update your configuration.yaml - example below. 8 | 9 | The contents of this repository should be in a directory named `iq_notify` inside `custom_components` of your Home Assistant. 10 | 11 | Therefore you must have access to your Home Assistant files, no Add-on avilable. 12 | 13 | ## How can you notify? 14 | 15 | - Notify only people present at home. (`only_home`) 16 | - Notify only people away from home. (`only_away`) 17 | - Notify people that just arrived home. (`just_arrived`) 18 | - Notify people that just left home. (`just_left`) 19 | - Notify people that are present at home for particular time. (`staying_home`) 20 | - Notify people that are away from home for particular time. (`staying_away`) 21 | - Try to notify people at home, but if none – notify people away. (`only_home_then_away`) 22 | - Try to notify people that just left home, but if none – notify people away. (`just_left_then_away`) 23 | 24 | ## Configuration reference 25 | 26 | ```yaml 27 | notify: 28 | - platform: iq_notify # the platform to use 29 | name: iphones # alias name for notify.{name} 30 | time: 2 # time offset in which we assume someone "just left/arrived" or "is staying" 31 | pairs: # a list of presence entities are corresponding notify services 32 | - entity: binary_sensor.presence_person1 # presence entity id #1 33 | service: person1_iphone # notify service to use for above entity, without domain (notify.) 34 | - entity: device_tracker.person2 # presence entity id #2 35 | service: person2_phone # notify service to use for above entity, without domain (notify.) 36 | ``` 37 | 38 | > `time` (defaults to 2) 39 | > 40 | > In minutes as offset to analyze if someone "just left/arrived". If someone will be at given state longer than given `time` – he/she won't be considered as someone that "just ...". This is also used for "staying home/away". Someone must be minimum of `time` in given state to be considered as "staying ...". 41 | 42 | > `entity` (required) 43 | > 44 | > ID of any entity that state for "present" is `on` or `home` and `off` or `not_home` for "away". 45 | > Can be `input_boolean`, `binary_sensor`, `group`, `switch`, `device_tracker` etc. 46 | 47 | > `service` (required) 48 | > 49 | > Is a service to use for notification without `notify.` domain. 50 | 51 | ## Example automations 52 | 53 | ##### Send notification only to people that are present at home. 54 | 55 | ```yaml 56 | - alias: "Notify: on garbage disposal" 57 | trigger: 58 | platform: state 59 | entity_id: calendar.garbage_disposal 60 | to: "on" 61 | condition: 62 | condition: state 63 | entity_id: binary_sensor.people_present 64 | state: "on" 65 | action: 66 | - service: notify.iphones 67 | data: 68 | title: Garbage disposal 69 | message: "{{ states.calendar.garbage_disposal.attributes.message }}" 70 | data: 71 | mode: only_home 72 | ``` 73 | 74 | If there is someone present – notify people that are present that today is the day of garbage disposal. We don't want to notify people that are away, because they wouldn't take the garbage out in front of the house. 75 | 76 | ##### Send notification to last person who just left home and this way armed the alarm. 77 | 78 | ```yaml 79 | automation: 80 | - alias: "Alarm: arm away when everyone left" 81 | trigger: 82 | platform: state 83 | entity_id: binary_sensor.people_present 84 | to: "off" 85 | action: 86 | - service: alarm_control_panel.alarm_arm_away 87 | entity_id: alarm_control_panel.alarm 88 | 89 | - alias: "Alarm: send notification on arming" 90 | trigger: 91 | - platform: state 92 | entity_id: alarm_control_panel.alarm 93 | to: "armed_away" 94 | action: 95 | - service: notify.iphones 96 | data: 97 | title: Alarm 98 | message: Alarm has been armed. 99 | data: 100 | mode: just_left 101 | ``` 102 | 103 | First automation tracks if everyone left home. If so – arms the alarm. After alarm is armed – second notification notifies only the person who "just left" that the alarm was armed successfully. The last person is responsible for arming the alarm. 104 | 105 | ##### Notify only people that are home, but if there are none – notify those away. 106 | 107 | ```yaml 108 | automation: 109 | - alias: "Door: remind on garage door kept opened" 110 | trigger: 111 | platform: state 112 | entity_id: binary_sensor.garage_door_contact 113 | to: "on" 114 | for: "00:05:00" 115 | action: 116 | - service: notify.iphones 117 | data: 118 | title: Garage door 119 | message: Garage door kept opened for 5mins. 120 | data: 121 | mode: only_home_then_away 122 | ``` 123 | 124 | Will let users that are home know about the door that is left open. If no one is home, let everyone know (because it might be a security breach). 125 | -------------------------------------------------------------------------------- /notify.py: -------------------------------------------------------------------------------- 1 | """Intelligent notifications based on presence.""" 2 | import logging 3 | from datetime import timedelta 4 | 5 | import voluptuous as vol 6 | 7 | import homeassistant.helpers.config_validation as cv 8 | import homeassistant.util.dt as dt_util 9 | from homeassistant.helpers.entity_component import EntityComponent 10 | from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME 11 | 12 | from homeassistant.components.notify import ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | DOMAIN = 'iq_notify' 17 | 18 | CONF_PAIRS = 'pairs' 19 | CONF_PAIR_ENTITY = 'entity' 20 | CONF_PAIR_SERVICE = 'service' 21 | CONF_TIME = 'time' 22 | CONF_MODE = 'mode' 23 | 24 | DEFAULT_TIME = 2 # minutes 25 | 26 | # send notification to all, default 27 | MODE_ALL = 'all' 28 | 29 | # send notification to only present users 30 | MODE_ONLY_HOME = 'only_home' 31 | 32 | # send notification to only away users 33 | MODE_ONLY_AWAY = 'only_away' 34 | 35 | # send notification to users that arrived in last CONF_TIME 36 | MODE_JUST_ARRIVED = 'just_arrived' 37 | 38 | # send notification to users that left in last CONF_TIME 39 | MODE_JUST_LEFT = 'just_left' 40 | 41 | # send notification to present users that are present for at least CONF_TIME 42 | MODE_STAYING_HOME = 'staying_home' 43 | 44 | # send notification to away users that are away for at least CONF_TIME 45 | MODE_STAYING_AWAY = 'staying_away' 46 | 47 | # try to send notification to present but if no one present - send to away users 48 | MODE_ONLY_HOME_THEN_AWAY = 'only_home_then_away' 49 | 50 | # try to send notification to users that left in last CONF_TIME but if that includes no one - send to away users 51 | MODE_JUST_LEFT_THEN_AWAY = 'just_left_then_away' 52 | 53 | 54 | PAIRS_CONFIG_SCHEMA = vol.Schema({ 55 | vol.Optional(CONF_PAIR_ENTITY): cv.string, 56 | vol.Optional(CONF_PAIR_SERVICE): cv.string, 57 | }) 58 | 59 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 60 | vol.Optional(CONF_PAIRS, default={}): vol.Any(cv.ensure_list, [PAIRS_CONFIG_SCHEMA]), 61 | vol.Optional(CONF_TIME, default=DEFAULT_TIME): cv.positive_int 62 | }) 63 | 64 | 65 | def get_service(hass, config, discovery_info=None): 66 | """Get the notification service.""" 67 | _LOGGER.debug('Setting up iq_notify platform...') 68 | 69 | pairs = config[CONF_PAIRS] 70 | time = config[CONF_TIME] 71 | 72 | return IqNotify(pairs, time) 73 | 74 | 75 | class IqNotify(BaseNotificationService): 76 | 77 | def __init__(self, pairs, time): 78 | self._pairs = pairs 79 | self._time = time 80 | 81 | def send_message(self, message="", **kwargs): 82 | """Send a message.""" 83 | 84 | print(kwargs) 85 | 86 | mode = MODE_ALL 87 | time = self._time 88 | data = {} 89 | 90 | # Get mode and time override 91 | if kwargs.get(ATTR_DATA) is not None: 92 | data = kwargs.get(ATTR_DATA) 93 | if data.get(CONF_MODE) is not None: 94 | mode = data.get(CONF_MODE) 95 | data.pop(CONF_MODE) 96 | if data.get(CONF_TIME) is not None: 97 | time = data.get(CONF_TIME) 98 | data.pop(CONF_TIME) 99 | 100 | _LOGGER.debug('IqNotify: using mode: ' + mode + ' and message: ' + message) 101 | 102 | looking_since = dt_util.utcnow() - timedelta(minutes=time) 103 | 104 | # Check if anyone is home (for MODE_ONLY_HOME_THEN_AWAY) or just left (for MODE_JUST_LEFT_THEN_AWAY) 105 | anyone_home = False 106 | anyone_just_left = False 107 | for pair in self._pairs: 108 | entity = pair.get(CONF_PAIR_ENTITY) 109 | if entity in self.hass.states._states: 110 | state = self.hass.states.get(entity) 111 | cur_state = state.state 112 | state_since = state.last_changed 113 | 114 | 115 | if cur_state == STATE_ON or cur_state == STATE_HOME: 116 | anyone_home = True 117 | elif looking_since < state_since: 118 | anyone_just_left = True 119 | 120 | 121 | service_data = kwargs 122 | # Append message 123 | service_data['message'] = message 124 | # Alter data 125 | service_data['data'] = data 126 | 127 | print(service_data) 128 | 129 | 130 | # Check and notify each entity 131 | for pair in self._pairs: 132 | entity = pair.get(CONF_PAIR_ENTITY) 133 | service = pair.get(CONF_PAIR_SERVICE) 134 | 135 | if entity in self.hass.states._states: 136 | state = self.hass.states.get(entity) 137 | cur_state = state.state 138 | state_since = state.last_changed 139 | 140 | if cur_state == STATE_ON or cur_state == STATE_HOME: 141 | user_is_home = True 142 | else: 143 | user_is_home = False 144 | 145 | notify = False 146 | 147 | if mode == MODE_ALL: 148 | notify = True 149 | elif mode == MODE_ONLY_HOME and user_is_home: 150 | notify = True 151 | elif mode == MODE_ONLY_AWAY and not user_is_home: 152 | notify = True 153 | elif mode == MODE_JUST_ARRIVED and user_is_home: 154 | if looking_since < state_since: 155 | notify = True 156 | elif mode == MODE_JUST_LEFT and not user_is_home: 157 | if looking_since < state_since: 158 | notify = True 159 | elif mode == MODE_STAYING_HOME and user_is_home: 160 | if looking_since > state_since: 161 | notify = True 162 | elif mode == MODE_STAYING_AWAY and not user_is_home: 163 | if looking_since > state_since: 164 | notify = True 165 | elif mode == MODE_ONLY_HOME_THEN_AWAY: 166 | if not anyone_home: 167 | notify = True 168 | elif anyone_home and user_is_home: 169 | notify = True 170 | elif mode == MODE_JUST_LEFT_THEN_AWAY: 171 | if not user_is_home: 172 | if not anyone_just_left: 173 | notify = True 174 | elif anyone_just_left: 175 | if looking_since < state_since: 176 | notify = True 177 | 178 | _LOGGER.debug('\nentity: ' + entity) 179 | _LOGGER.debug('cur_state: ' + str(cur_state)) 180 | _LOGGER.debug('looking_since: ' + str(looking_since)) 181 | _LOGGER.debug('state_since: ' + str(state_since)) 182 | _LOGGER.debug('looking_since < state_since: ' + str(looking_since < state_since)) 183 | _LOGGER.debug('user_is_home: ' + str(user_is_home)) 184 | _LOGGER.debug('anyone_just_left: ' + str(anyone_just_left)) 185 | 186 | if notify: 187 | self.hass.services.call('notify', service, service_data) 188 | _LOGGER.info('Notifying notify.' + service + ' via ' + mode + ' mode' + '\n'); 189 | --------------------------------------------------------------------------------