-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstate_enforcer.yaml
More file actions
195 lines (183 loc) · 9.58 KB
/
state_enforcer.yaml
File metadata and controls
195 lines (183 loc) · 9.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# -------------------------------------------------------------------------------------------------
# Package State Enforcer - Ver. 1.0.1
# Description: Turn on or off an entity and check multiple times that the state is what you expect to be,
# retrying to change its state, until a specified timeout occurs.
# If after the timeout it could not update the desired state an actionable notification is made that let
# you retry or cancel.
# In case of failure and during the operation a persistent notification is generated
# Article with full details (italian language): TODO
# Repository: https://github.com/energywave/ha-state-enforcer
# -------------------------------------------------------------------------------------------------
# Author: Henrik Sozzi
# Site: https://henriksozzi.it
# -------------------------------------------------------------------------------------------------
# CHANGELOG: please see the CHANGELOG.md file
# -------------------------------------------------------------------------------------------------
# Distributed with MIT license. PLease see LICENSE file for more details.
# -------------------------------------------------------------------------------------------------
# Please refer to the readme visible here: https://github.com/energywave/ha-state-enforcer/blob/main/README.md
# DEPENDENCIES
# This package requires the following components to be installed and working:
# - Multinotify (to make notifications. You can replace state_enforcer_notifications script code
# with your own if you don't want to use multinotify)
# * Get it here: https://github.com/energywave/multinotify )
# - set_state script to create and set dynamic entities. PAY ATTENTION that several versions of
# that script exists! You must use the version with the allow_create argument!
# * Get it from @xannor repo here:
# https://github.com/xannor/hass_py_set_state
# * Or get it on my blog with explanation in IT language here:
# https://henriksozzi.it/2021/04/impostare-lo-stato-di-unentita-in-home-assistant/
# IMPORTANT NOTE ABOUT ASYNC USE
# Call the action script.turn_on and NOT directly with the action script.state_enforcer
# as in the first case the execution is asynchronous while in the second case it will
# be synchronous. What does it mean? If you specify for example 90 seconds timeout it could stop your
# script or automation for 90 seconds!
# By using script.turn_on, however, the execution instantly continue on your script or automation
# and this script will work in background.
# Example about how to use it:
# action: script.turn_on
# continue_on_error: true
# target:
# entity_id: script.state_enforcer
# data:
# variables:
# entity_id: light.living_room
# action: off
# timeout: 30
# The timeout argument is not required. If you don't specify it it will be assumed 30 seconds.
script:
# State Enforcer script. Use it the async way with action: script.turn_on, not by calling it directly in the action! Please see header example or readme on github.
state_enforcer:
alias: "Turn on or off an entity and ensure the state"
description: "Turn on or off an entity and then check that the state was changed and notify if that wasn't done after the specified time"
mode: parallel
max: 10
fields:
entity_id:
description: "Entity to turn on or off"
example: "light.living_room"
required: true
selector:
entity:
filter:
domain:
- light
- switch
- group
action:
description: "Action to execute (on/off)"
example: "off"
required: true
selector:
select:
options:
- "on"
- "off"
timeout:
description: "Timeout to ensure the state"
example: "30"
required: false
selector:
number:
min: 5
max: 300
step: 5
unit_of_measurement: "seconds"
variables:
service_name: "{{ 'homeassistant.turn_on' if action == 'on' else 'homeassistant.turn_off' }}"
expected_state: "{{ action }}" # "on" or "off"
entity_lock: "tmp.lock_toggle_{{ entity_id | replace('.', '_') }}" # Entity name we will use to lock the execution based on the specific entity
timeout_sec: "{{ timeout | default(30) }}" # Timeout with default value
start_time: "{{ now().timestamp() }}" # Start time we will use to evaluate time elapsed for timeout
sequence:
# We create a persistent notification during the execution of the script so that we know if there is a State Enforcer instance running
- alias: "Create a persistent notification"
action: persistent_notification.create
continue_on_error: true
data:
title: State enforcer running
message: "Enforcing state **{{ expected_state }}** for **{{ states[entity_id].name }}** (max {{ timeout_sec }} seconds)..."
notification_id: "{{ entity_id }}"
# We stop other instance of this script that is working on the same entity, if present.
# So if wanted to turn on a light and, while trying to do it, we issue the command to turn it off this will shit down the turn on trial and start the turn off one.
# It's like having "mode: restart" for the script but based on the specific entity you requested (Home Assistant don't have such a feature, we implement it in that way)
- alias: "Stop another instance on the same light, if running"
if: "{{ is_state(entity_lock, 'running') }}"
then:
- alias: "Stop other instance"
action: python_script.set_state
continue_on_error: true
data:
allow_create: true
entity_id: "{{ entity_lock }}"
state: "stop"
- wait_template: "{{ is_state(entity_lock, 'stopped') }}"
timeout: "00:06:00"
continue_on_timeout: true
- if: "{{ not is_state(entity_lock, 'stopped') }}"
then:
- alias: "Inform of the failure"
action: persistent_notification.create
continue_on_error: true
data:
title: State enforcer warning
message: >
**{{ now().timestamp() | timestamp_custom("%Y-%m-%d %H:%M:%S") }}**
State enforcer could not stop a previous instance while enforcing state **{{ expected_state }}** of **{{ states[entity_id].name }}**.
Current entity lock value: **{{ states(entity_lock) }}**"
# Here we set the lock that allows us to do what described in the previous block
- alias: "Set the lock"
action: python_script.set_state
continue_on_error: true
data:
allow_create: true
entity_id: "{{ entity_lock }}"
state: "running"
# This is the real core: a cycle that will exit when the state is what we desire, the lock is telling us to stop (another instance) or timeout occured
- alias: "Cycle to do the action and check the state"
repeat:
while:
- "{{ not is_state(entity_id, expected_state) }}" # State check
- "{{ is_state(entity_lock, 'running') }}" # Lock check
- "{{ (now().timestamp() - start_time) | int < timeout }}" # Timeout check
sequence:
- alias: "Try to set the state"
action: "{{ service_name }}"
continue_on_error: true
target:
entity_id: "{{ entity_id }}"
- alias: "Wait 5 sec but exit if the lock requests to stop"
wait_template: "{{ is_state(entity_lock, 'stop') or is_state(entity_id, expected_state) }}"
timeout: "00:00:05"
continue_on_timeout: true
# If we failed to set the state, there isn't another instance and timeout elapsed then we will call the state_enforcer_notifications script, in the other file
- alias: "If a timeout setting entity state happened: notify it to the user"
if: "{{ not is_state(entity_id, expected_state) and is_state(entity_lock, 'running') and (now().timestamp() - start_time) | int >= timeout}}"
then:
- alias: "Async notification about the failure"
action: script.turn_on
continue_on_error: true
target:
entity_id: script.state_enforcer_notifications
data:
variables:
entity_id: "{{ entity_id }}"
expected_state: "{{ expected_state }}"
timeout: "{{ timeout_sec }}"
# We remove the persistent notification that is telling us there is an instance of State Enforcer running before setting state to stopped (that will continue other instance, if present)
- alias: "If there isn't another instance we remove the running notification"
if: "{{ is_state(entity_lock, 'running') }}"
then:
- alias: "Chear the running persistent notification"
action: persistent_notification.dismiss
continue_on_error: true
data:
notification_id: "{{ entity_id }}"
# Now we remove the lock as the script is ended, allowing new instances to know there is no other instance running
- alias: "Remove the lock"
action: python_script.set_state
continue_on_error: true
data:
allow_create: true
entity_id: "{{ entity_lock }}"
state: "stopped"