motion_detected_lights.yaml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. blueprint:
  2. domain: automation
  3. name: Motion detected lights
  4. description: >-
  5. Activate lights based on binary_sensors. This is typically a motion sensor, but can also be a door sensor.
  6. The automation features:
  7. - dim light(s) before turning them off to alert present people that the sensors have become "off" and
  8. the automation will turn off the lights. The current states of each lights is stored in a scene, so
  9. if the ones in that room then move - the lights will dim back to the current state by applying the
  10. stored scene.
  11. - a parallel thread that will listen for light changes, e.g: The automation is triggered multiple times
  12. whenever anyone is in the room. If you have adjusted the light level or turned off any lights while
  13. the automation is running, these changes are stored to the scene, so the lights will stay in same state.
  14. - «wait for action» either before the lights are dimmed or while the lights are dimmed down. This is
  15. perfect to use when the house state is in «cleaning mode», while someone is showering (high humdity)
  16. and the motion sensor can't register motion because of the steam etc.
  17. input:
  18. trigger_sensors:
  19. name: Trigger sensors
  20. description: >-
  21. Sensors that will trigger this automation. It can be a motion sensor, door sensor etc. Any areas
  22. or devices that is given in this input will be crawled for binary_sensors, so it's recommended
  23. to only list entities explicitly with their entity_id.
  24. selector:
  25. entity:
  26. domain: binary_sensor
  27. multiple: true
  28. synced_lights:
  29. name: Synced lights
  30. description: >-
  31. Lights that will syncronize the trigger sensors. Any areas or devices that is given in this input
  32. will be crawled for light entities, so it's recommended to only list entities explicitly with their
  33. entity_id.
  34. selector:
  35. entity:
  36. domain: light
  37. illuminance_sensors:
  38. name: Lux sensors
  39. description: >-
  40. Lux (illuminance) sensor to monitor. If the threshold are met, the lights will stay off. The average
  41. illuminance will be calculated if several devices is set.
  42. default: []
  43. selector:
  44. entity:
  45. domain: sensor
  46. device_class: illuminance
  47. illuminance_threshold:
  48. name: Lux threshold
  49. description: >-
  50. Threshold before lights are turned on. Default (-1) means that the lux threshold won't be considered.
  51. default: -1.0
  52. selector:
  53. number:
  54. max: 100.0
  55. min: -1.0
  56. step: 0.1
  57. dim_percentage:
  58. name: Dim-percentage
  59. description: >-
  60. Dim lights to this level (%) of current light level before turning lights off. Current level
  61. will be stored in a scene. Setting this to 0.0 or 1.0 will skip this action.
  62. default: 0.3
  63. selector:
  64. number:
  65. max: 1.0
  66. min: 0.0
  67. step: 0.01
  68. after_wait_actions:
  69. name: Wait actions
  70. description: >-
  71. Actions to run to delay further, e.g wait until media player state is off, housekeeping mode
  72. is off, humidity is back down etc.
  73. default: []
  74. selector:
  75. action:
  76. wait_actions_before_dim:
  77. name: Wait actions before dim
  78. description: >-
  79. Run wait actions before dimming down (not turning off). Set to false/OFF if you want wait
  80. actions to run after dimming instead, but before turning OFF completely.
  81. default: "{{ true }}"
  82. selector:
  83. boolean:
  84. delay:
  85. name: Delay
  86. description: >-
  87. Time (minutes) to wait to turn off after last movement was detected.
  88. selector:
  89. number:
  90. min: 0.0
  91. max: 300.0
  92. step: 5.0
  93. default: 3
  94. mode: restart
  95. trigger_variables:
  96. trigger_sensors_input: !input trigger_sensors
  97. trigger_sensors: >-
  98. {{ trigger_sensors_input|unique|sort }}
  99. scene_name: >-
  100. {{ this.entity_id.replace('.', '_') }}
  101. synced_lights_input: !input synced_lights
  102. synced_lights: >-
  103. {{ synced_lights_input|unique|sort }}
  104. trigger:
  105. ############ Hack for ############
  106. # - platform: state #
  107. # entity_id: "{{ sensors }}" #
  108. ##################################
  109. # - platform: template
  110. # value_template: >-
  111. # {% set sensors = namespace(triggered=false) %}
  112. # {% for sensor in trigger_sensors if is_state(sensor, 'on') %}
  113. # {% set sensors.triggered = true %}
  114. # {% endfor %}
  115. # {{ sensors.triggered }}
  116. - platform: state
  117. # from: "off"
  118. to: "on"
  119. entity_id: !input trigger_sensors
  120. - platform: event
  121. event_type: "motion_detected_lights"
  122. event_data:
  123. end: "{{ scene_name }}"
  124. variables:
  125. illuminance_sensors_input: !input illuminance_sensors
  126. illuminance_sensors: >-
  127. {{ illuminance_sensors_input|unique|sort }}
  128. # {% set sensors = namespace(entities=[]) %}
  129. # {% if 'entity_id' in illuminance_sensors_input %}
  130. # {% for entity in ([ illuminance_sensors_input['entity_id'] ] if illuminance_sensors_input['entity_id'] is string else illuminance_sensors_input['entity_id']) %}
  131. # {% set sensors.entities = sensors.entities + [entity] %}
  132. # {% endfor %}
  133. # {% endif %}
  134. # {% if 'area_id' in illuminance_sensors_input %}
  135. # {% for area in ([ illuminance_sensors_input['area_id'] ] if illuminance_sensors_input['area_id'] is string else illuminance_sensors_input['area_id']) %}
  136. # {% for entity in area_entities(area) if state_attr(entity, 'device_class') == 'illuminance' %}
  137. # {% set sensors.entities = sensors.entities + [entity] %}
  138. # {% endfor %}
  139. # {% endfor %}
  140. # {% endif %}
  141. # {% if 'device_id' in trigger_sensors_input %}
  142. # {% for device in ([ illuminance_sensors_input['device_id'] ] if illuminance_sensors_input['device_id'] is string else illuminance_sensors_input['device_id']) %}
  143. # {% for entity in device_entities(device) if state_attr(entity, 'device_class') == 'illuminance' %}
  144. # {% set sensors.entities = sensors.entities + [entity] %}
  145. # {% endfor %}
  146. # {% endfor %}
  147. # {% endif %}
  148. # {{ sensors.entities|unique|list }}
  149. illuminance_threshold: !input illuminance_threshold
  150. invalid_light: >- # Used to invalidate scene as there's no other way to do it at the moment
  151. {{ states.light|rejectattr('entity_id', 'in', synced_lights)|map(attribute='entity_id')|first }}
  152. log_level: warning
  153. dim_percentage: !input dim_percentage
  154. delay_minutes: !input delay
  155. delay_seconds: "{{ delay_minutes * 60 }}"
  156. wait_actions_before_dim: !input wait_actions_before_dim
  157. action:
  158. - alias: "Triggered on motion or event"
  159. if: "{{ trigger.platform != 'event' }}"
  160. then:
  161. - alias: "Test if threshold is met"
  162. choose:
  163. conditions:
  164. - condition: template
  165. value_template: "{{ illuminance_threshold != -1 and illuminance_sensors|count > 0 }}"
  166. - condition: template
  167. value_template: >- # If any lights are on... Continue so they eventually will turn off
  168. {% set lights = namespace(on=false) %}
  169. {% for light in synced_lights if is_state(light, 'on') %}
  170. {% set lights.on = true %}
  171. {% endfor %}
  172. {{ not lights.on }}
  173. - condition: template
  174. value_template: >-
  175. {% set lux = namespace(values=[]) %}
  176. {% for sensor in illuminance_sensors %}
  177. {% set lux.values = lux.values + [ states(sensor)|float ] %}
  178. {% endfor %}
  179. {{ (lux.values|sum / lux.values|count)|int > illuminance_threshold }}
  180. sequence:
  181. stop: "Illuminance threshold met"
  182. - alias: "Motion: Turn on exisiting scene or turn on lights."
  183. continue_on_error: true # if scenes are empty (might happen after a scene.reload)
  184. choose:
  185. conditions: "{{ states.scene | selectattr('attributes.friendly_name', 'eq', scene_name) | list | count == 1 and synced_lights == state_attr('scene.' + scene_name, 'entity_id')|sort }}"
  186. sequence: # Scene do exist and is valid
  187. - service: system_log.write
  188. data:
  189. level: "{{ log_level }}"
  190. logger: "{{ scene_name }}"
  191. message: >-
  192. Scene «{{ scene_name }}» already exists and contains all lights. Restore scene.
  193. - alias: "Motion: Turn on existing scene for synced lights"
  194. service: scene.turn_on
  195. target:
  196. entity_id: "scene.{{scene_name}}"
  197. default: # Scene does not exist
  198. - service: system_log.write
  199. continue_on_error: true
  200. data:
  201. level: "{{ log_level }}"
  202. logger: "{{ scene_name }}"
  203. message: >-
  204. Scene «{{ scene_name }} does NOT exist or is INVALID.
  205. Entities to sync: {{ synced_lights }} => Turn ON every light sourcce.
  206. - alias: "Motion: Turn on synced lights"
  207. service: light.turn_on
  208. target:
  209. entity_id: "{{ synced_lights }}"
  210. - alias: >-
  211. Listen for light changes and motion in parallel. Save snapshots to scene on
  212. light changes. we dont restore light every time the motion is triggered without
  213. being OFF. End this part of automation when motion stops.
  214. parallel:
  215. - alias: >-
  216. Listen for light changes for as long as this automation lives,
  217. means: until event is fired and automation is restarted.
  218. repeat:
  219. while: "{{ true }}"
  220. sequence:
  221. - alias: "Store variable with current light values (on or brightness)"
  222. variables:
  223. synced_lights_values: >-
  224. {% set lights = namespace(values=[]) %}
  225. {% for light in synced_lights %}
  226. {%if is_state_attr(light, 'color_mode', 'brightness') %}
  227. {% set lights.values = lights.values + [state_attr(light, 'brightness')] %}
  228. {% else %}
  229. {% set lights.values = lights.values + [states(light)] %}
  230. {% endif %}
  231. {% endfor %}
  232. {{ lights.values }}
  233. - alias: "Wait for change in any light source"
  234. wait_template: >-
  235. {% set lights = namespace(changed=false) %}
  236. {% for light in synced_lights %}
  237. {%if is_state_attr(light, 'color_mode', 'brightness') and state_attr(light, 'brightness') != synced_lights_values[loop.index - 1] %}
  238. {% set lights.changed = true %}
  239. {% elif not is_state_attr(light, 'color_mode', 'brightness') and states(light) != synced_lights_values[loop.index - 1] %}
  240. {% set lights.changed = true %}
  241. {% endif %}
  242. {% endfor %}
  243. {{ lights.changed }}
  244. # Check here if values = 0 / OFF
  245. - service: system_log.write
  246. data:
  247. level: "{{ log_level }}"
  248. logger: "{{ scene_name }}"
  249. message: >-
  250. Light levels changed: {{ synced_lights }} from {{ synced_lights_values }}.
  251. Saving snapshot to scene «{{ scene_name }}».
  252. - alias: "Store variable a variable with ON lights."
  253. variables:
  254. synced_lights_on: >-
  255. {% set lights = namespace(on=[]) %}
  256. {% for light in synced_lights if not is_state(light, 'off') %}
  257. {% set lights.on = lights.on + [light] %}
  258. {% endfor %}
  259. {{ lights.on }}
  260. - choose:
  261. conditions: "{{ synced_lights_on|count == 0 }}"
  262. sequence:
  263. service: system_log.write
  264. data:
  265. level: "{{ log_level }}"
  266. logger: "{{ scene_name }}"
  267. message: "No lights on: invalidating with {{ invalid_light }} which isn't in {{ synced_lights }}."
  268. - alias: "Save scene for current change and return to loop. If no lights are ON; invalidate scene."
  269. service: scene.create
  270. data:
  271. scene_id: "{{ scene_name }}"
  272. snapshot_entities: >-
  273. {{ synced_lights if synced_lights_on|count != 0 else [ invalid_light ] }}
  274. - alias: >-
  275. Listen for motion change, and wait for it to become 'OFF',
  276. wait for the delay and actions to finish, and then send event to kill parallelism.
  277. sequence: # @ignore: Missing property "condition"
  278. - alias: "Wait for motion to end."
  279. wait_template: >-
  280. {% set sensors = namespace(triggered=false) %}
  281. {% for sensor in trigger_sensors if is_state(sensor, 'on') %}
  282. {% set sensors.triggered = true %}
  283. {% endfor %}
  284. {{ not sensors.triggered }}
  285. - service: system_log.write
  286. data:
  287. level: "{{ log_level }}"
  288. logger: "{{ scene_name }}"
  289. message: >-
  290. No motion detected by {{ trigger_sensors }}. Run wait actions
  291. wait {{ (delay_seconds * 1/3)|int }} seconds,, then end
  292. automation (parallelism) by issuing an event, and let automation «re-trigger».
  293. - alias: "Run and wait for actions to finish"
  294. choose:
  295. conditions: >-
  296. {{ wait_actions_before_dim }}
  297. sequence: !input after_wait_actions
  298. - alias: "Wait for delay to fire event."
  299. delay:
  300. seconds: "{{ (delay_seconds * 2/3)|int }}"
  301. - alias: "Ending: Fire event to restart automation and kill parallel processes"
  302. event: "motion_detected_lights"
  303. event_data:
  304. end: "{{ scene_name }}"
  305. else: # Event was fired
  306. - service: system_log.write
  307. data:
  308. level: "{{ log_level }}"
  309. logger: "{{ scene_name }}"
  310. message: "Ending {{ scene_name }}, dim down and eventually turn off {{ synced_lights }}."
  311. - alias: "Calculate average brightness and set dim level"
  312. variables:
  313. brightness: >-
  314. {% set brightness = namespace(levels=[]) %}
  315. {% for light in synced_lights if is_state(light, 'on') %}
  316. {% if is_state_attr(light, 'color_mode', 'brightness') and state_attr(light, 'brightness')|int != 0 %}
  317. {% set brightness.levels = brightness.levels + [ state_attr(light, 'brightness') ] %}
  318. {% else %}
  319. {% set brightness.levels = brightness.levels + [ 100 ] %}
  320. {% endif %}
  321. {% endfor %}
  322. {{ 0.0 if brightness.levels|length == 0 else brightness.levels|sum / brightness.levels|length }}
  323. dim_level: "{{ (brightness * dim_percentage)|int }}"
  324. - service: system_log.write
  325. data:
  326. level: "{{ log_level }}"
  327. logger: "{{ scene_name }}"
  328. message: >-
  329. Light avg-brightness is: {{ brightness }}, now dimming lights
  330. with {{ (dim_percentage * 100)|int }}% to {{ dim_level }}.
  331. - alias: "Dim lights to a low brightness to alert no motion"
  332. service: light.turn_on
  333. target:
  334. entity_id: >-
  335. {{ synced_lights }}
  336. data:
  337. brightness: "{{ dim_level }}"
  338. - alias: "Run and wait for actions to finish"
  339. choose:
  340. conditions: >-
  341. {{ not wait_actions_before_dim }}
  342. sequence: !input after_wait_actions
  343. - alias: "Wait for dim to be recognized by anyone before turning off lights"
  344. delay:
  345. seconds: "{{ (delay_seconds * 1/3)|int }}"
  346. - alias: "Turn off lights"
  347. service: light.turn_off
  348. target:
  349. entity_id: "{{ synced_lights }}"
  350. - service: system_log.write
  351. data:
  352. level: "{{ log_level }}"
  353. logger: "{{ scene_name }}"
  354. message: >-
  355. Lights {{ synced_lights }} turned off.