motion_detected_lights.yaml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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. - platform: state
  106. # from: "off"
  107. to: "on"
  108. entity_id: !input trigger_sensors
  109. - platform: event
  110. event_type: "motion_detected_lights"
  111. event_data:
  112. end: "{{ scene_name }}"
  113. variables:
  114. illuminance_sensors_input: !input illuminance_sensors
  115. illuminance_sensors: >-
  116. {{ illuminance_sensors_input|unique|sort }}
  117. # {% set sensors = namespace(entities=[]) %}
  118. # {% if 'entity_id' in illuminance_sensors_input %}
  119. # {% for entity in ([ illuminance_sensors_input['entity_id'] ] if illuminance_sensors_input['entity_id'] is string else illuminance_sensors_input['entity_id']) %}
  120. # {% set sensors.entities = sensors.entities + [entity] %}
  121. # {% endfor %}
  122. # {% endif %}
  123. # {% if 'area_id' in illuminance_sensors_input %}
  124. # {% for area in ([ illuminance_sensors_input['area_id'] ] if illuminance_sensors_input['area_id'] is string else illuminance_sensors_input['area_id']) %}
  125. # {% for entity in area_entities(area) if state_attr(entity, 'device_class') == 'illuminance' %}
  126. # {% set sensors.entities = sensors.entities + [entity] %}
  127. # {% endfor %}
  128. # {% endfor %}
  129. # {% endif %}
  130. # {% if 'device_id' in trigger_sensors_input %}
  131. # {% for device in ([ illuminance_sensors_input['device_id'] ] if illuminance_sensors_input['device_id'] is string else illuminance_sensors_input['device_id']) %}
  132. # {% for entity in device_entities(device) if state_attr(entity, 'device_class') == 'illuminance' %}
  133. # {% set sensors.entities = sensors.entities + [entity] %}
  134. # {% endfor %}
  135. # {% endfor %}
  136. # {% endif %}
  137. # {{ sensors.entities|unique|list }}
  138. illuminance_threshold: !input illuminance_threshold
  139. invalid_light: >- # Used to invalidate scene as there's no other way to do it at the moment
  140. {{ states.light|rejectattr('entity_id', 'in', synced_lights)|map(attribute='entity_id')|first }}
  141. log_level: warning
  142. dim_percentage: !input dim_percentage
  143. delay_minutes: !input delay
  144. delay_seconds: "{{ delay_minutes * 60 }}"
  145. wait_actions_before_dim: !input wait_actions_before_dim
  146. action:
  147. - alias: "Triggered on motion or event"
  148. if: "{{ trigger.platform != 'event' }}"
  149. then:
  150. - alias: "Test if threshold is met"
  151. choose:
  152. conditions:
  153. - condition: template
  154. value_template: "{{ illuminance_threshold != -1 and illuminance_sensors|count > 0 }}"
  155. - condition: template
  156. value_template: >- # If any lights are on... Continue so they eventually will turn off
  157. {% set lights = namespace(on=false) %}
  158. {% for light in synced_lights if is_state(light, 'on') %}
  159. {% set lights.on = true %}
  160. {% endfor %}
  161. {{ not lights.on }}
  162. - condition: template
  163. value_template: >-
  164. {% set lux = namespace(values=[]) %}
  165. {% for sensor in illuminance_sensors %}
  166. {% set lux.values = lux.values + [ states(sensor)|float ] %}
  167. {% endfor %}
  168. {{ (lux.values|sum / lux.values|count)|int > illuminance_threshold }}
  169. sequence:
  170. stop: "Illuminance threshold met"
  171. - alias: "Motion: Turn on exisiting scene or turn on lights."
  172. continue_on_error: true # if scenes are empty (might happen after a scene.reload)
  173. choose:
  174. conditions: "{{ states.scene|selectattr('attributes.friendly_name', 'eq', scene_name)|list|count == 1 and synced_lights == state_attr('scene.' + scene_name, 'entity_id')|sort }}"
  175. sequence: # Scene do exist and is valid
  176. - service: system_log.write
  177. data:
  178. level: "{{ log_level }}"
  179. logger: "{{ scene_name }}"
  180. message: >-
  181. Scene «{{ scene_name }}» already exists and contains all lights. Restore scene.
  182. - alias: "Motion: Turn on existing scene for synced lights"
  183. service: scene.turn_on
  184. target:
  185. entity_id: "scene.{{scene_name}}"
  186. default: # Scene does not exist
  187. - service: system_log.write
  188. continue_on_error: true
  189. data:
  190. level: "{{ log_level }}"
  191. logger: "{{ scene_name }}"
  192. message: >-
  193. Scene «{{ scene_name }} does NOT exist or is INVALID.
  194. Entities to sync: {{ synced_lights }} => Turn ON every light sourcce.
  195. - alias: "Motion: Turn on synced lights"
  196. service: light.turn_on
  197. target:
  198. entity_id: "{{ synced_lights }}"
  199. - alias: >-
  200. Listen for light changes and motion in parallel. Save snapshots to scene on
  201. light changes. we dont restore light every time the motion is triggered without
  202. being OFF. End this part of automation when motion stops.
  203. parallel:
  204. - alias: >-
  205. Listen for light changes for as long as this automation lives,
  206. means: until event is fired and automation is restarted.
  207. repeat:
  208. while: "{{ true }}"
  209. sequence:
  210. - alias: "Store variable with current light values (on or brightness)"
  211. variables:
  212. synced_lights_values: >-
  213. {% set lights = namespace(values=[]) %}
  214. {% for light in synced_lights %}
  215. {%if is_state_attr(light, 'color_mode', 'brightness') %}
  216. {% set lights.values = lights.values + [state_attr(light, 'brightness')] %}
  217. {% else %}
  218. {% set lights.values = lights.values + [states(light)] %}
  219. {% endif %}
  220. {% endfor %}
  221. {{ lights.values }}
  222. - alias: "Wait for change in any light source"
  223. wait_template: >-
  224. {% set lights = namespace(changed=false) %}
  225. {% for light in synced_lights %}
  226. {%if is_state_attr(light, 'color_mode', 'brightness') and state_attr(light, 'brightness') != synced_lights_values[loop.index - 1] %}
  227. {% set lights.changed = true %}
  228. {% elif not is_state_attr(light, 'color_mode', 'brightness') and states(light) != synced_lights_values[loop.index - 1] %}
  229. {% set lights.changed = true %}
  230. {% endif %}
  231. {% endfor %}
  232. {{ lights.changed }}
  233. # Check here if values = 0 / OFF
  234. - service: system_log.write
  235. data:
  236. level: "{{ log_level }}"
  237. logger: "{{ scene_name }}"
  238. message: >-
  239. Light levels changed: {{ synced_lights }} from {{ synced_lights_values }}.
  240. Saving snapshot to scene «{{ scene_name }}».
  241. - alias: "Store a variable with ON lights."
  242. variables:
  243. synced_lights_on: >-
  244. {% set lights = namespace(on=[]) %}
  245. {% for light in synced_lights if not is_state(light, 'off') %}
  246. {% set lights.on = lights.on + [light] %}
  247. {% endfor %}
  248. {{ lights.on }}
  249. - choose:
  250. conditions: "{{ synced_lights_on|count == 0 }}"
  251. sequence:
  252. service: system_log.write
  253. data:
  254. level: "{{ log_level }}"
  255. logger: "{{ scene_name }}"
  256. message: "No lights on: invalidating with {{ invalid_light }} which isn't in {{ synced_lights }}."
  257. - alias: "Save scene for current change and return to loop. If no lights are ON; invalidate scene."
  258. service: scene.create
  259. data:
  260. scene_id: "{{ scene_name }}"
  261. snapshot_entities: >-
  262. {{ [ invalid_light ] if synced_lights_on|count == 0 else synced_lights }}
  263. - sequence: # @ignore: Missing property "condition"
  264. # alias: >-
  265. # Listen for motion change, and wait for it to become 'OFF',
  266. # wait for the delay and actions to finish, and then send event to kill parallelism.
  267. - alias: "Wait for motion to end."
  268. wait_template: >-
  269. {% set sensors = namespace(triggered=false) %}
  270. {% for sensor in trigger_sensors if is_state(sensor, 'on') %}
  271. {% set sensors.triggered = true %}
  272. {% endfor %}
  273. {{ not sensors.triggered }}
  274. - service: system_log.write
  275. data:
  276. level: "{{ log_level }}"
  277. logger: "{{ scene_name }}"
  278. message: >-
  279. No motion detected by {{ trigger_sensors }}. Run wait actions
  280. wait {{ (delay_seconds * 2/3)|int }} seconds,, then end
  281. automation (parallelism) by issuing an event, and let automation «re-trigger».
  282. - alias: "Run and wait for actions to finish"
  283. choose:
  284. conditions: >-
  285. {{ wait_actions_before_dim }}
  286. sequence: !input after_wait_actions
  287. - alias: "Wait for delay to fire event."
  288. delay:
  289. seconds: "{{ (delay_seconds * 2/3)|int }}"
  290. - alias: "Ending: Fire event to restart automation and kill parallel processes"
  291. event: "motion_detected_lights"
  292. event_data:
  293. end: "{{ scene_name }}"
  294. else: # Event was fired
  295. - service: system_log.write
  296. data:
  297. level: "{{ log_level }}"
  298. logger: "{{ scene_name }}"
  299. message: "Ending {{ scene_name }}, dim down and eventually turn off {{ synced_lights }}."
  300. - alias: "Calculate average brightness and set dim level"
  301. variables:
  302. brightness: >-
  303. {% set brightness = namespace(levels=[]) %}
  304. {% for light in synced_lights if is_state(light, 'on') %}
  305. {% if is_state_attr(light, 'color_mode', 'brightness') and state_attr(light, 'brightness')|int != 0 %}
  306. {% set brightness.levels = brightness.levels + [ state_attr(light, 'brightness') ] %}
  307. {% else %}
  308. {% set brightness.levels = brightness.levels + [ 100 ] %}
  309. {% endif %}
  310. {% endfor %}
  311. {{ 0.0 if brightness.levels|length == 0 else brightness.levels|sum / brightness.levels|length }}
  312. dim_level: "{{ (brightness * dim_percentage)|int }}"
  313. - service: system_log.write
  314. data:
  315. level: "{{ log_level }}"
  316. logger: "{{ scene_name }}"
  317. message: >-
  318. Light avg-brightness is: {{ brightness }}, now dimming lights
  319. with {{ (dim_percentage * 100)|int }}% to {{ dim_level }}.
  320. - alias: "Dim lights to a low brightness to alert no motion"
  321. service: light.turn_on
  322. target:
  323. entity_id: >-
  324. {{ synced_lights }}
  325. data:
  326. brightness: "{{ dim_level }}"
  327. - alias: "Run and wait for actions to finish"
  328. choose:
  329. conditions: >-
  330. {{ not wait_actions_before_dim }}
  331. sequence: !input after_wait_actions
  332. - alias: "Wait for dim to be recognized by anyone before turning off lights"
  333. delay:
  334. seconds: "{{ (delay_seconds * 1/3)|int }}"
  335. - alias: "Turn off lights"
  336. service: light.turn_off
  337. target:
  338. entity_id: "{{ synced_lights }}"
  339. - service: system_log.write
  340. data:
  341. level: "{{ log_level }}"
  342. logger: "{{ scene_name }}"
  343. message: >-
  344. Lights {{ synced_lights }} turned off.