-
Notifications
You must be signed in to change notification settings - Fork 0
/
AirExchange.yaml
312 lines (304 loc) · 12.4 KB
/
AirExchange.yaml
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
blueprint:
name: AirExchange
description: >
Automation for outside air exchange. All measurements must be in same format (ie. don't mix celsius and farenheit.)
**Version**: 2024.7.1-49
domain: automation
source_url: https://github.com/rwalker777/HA-Blueprints/blob/main/AirExchange.yaml
homeassistant:
min_version: 2024.6.0
input:
Outdoor:
name: Outdoor temperature, humidity and air quality
input:
outdoor_temp_entity:
name: Outdoor temperature
description: Outdoor temperature source
selector:
entity:
filter:
- device_class: temperature
#- domain: weather
temp_start_delta:
name: Outdoor temperature delta to indoor temperature for air exchange - starts/stop when outside this delta
description: This delta prevents the fan from running when small temperature differential inside vs outside
default: 3
selector:
number:
min: 1
max: 30
unit_of_measurement: degree
temp_stop_delta:
name: >
Outdoor temperature delta to indoor temperature to stop air exchange **must be lower than start**
description: This delta stops the fans from running forever - set to zero for fans to always run
default: 2
selector:
number:
min: 0
max: 30
unit_of_measurement: degree
# outdoor_humidity_entity:
# name: Outdoor humidity
# description: Outdoor humidity source
# selector:
# entity:
# - device_class: humidity
# - domain: weather
outdoor_aqi:
name: Outdoor air quality
description: Outdoor air quality source
default: []
selector:
entity:
filter:
- device_class: aqi
outdoor_aqi_level:
name: Outdoor air quality level
description: Outdoor air quality maximum level - above this fresh air is disabled
default: 151
selector:
number:
min: 50
max: 300
Indoor:
name: Indoor temperature, humidity and CO2
input:
fan_entity:
name: Fan
description: Entity to control Fan - can be a switch, fan
selector:
entity:
domain:
- fan
- switch
max_fan_runtime:
name: Maximum time in hours for fan to run
description: Failsafe to prevent fan from running indefinitely - set to zero to disable
default: 8
selector:
number:
min: 0
max: 24
indoor_temp:
name: Indoor temperature
description: Indoor temperature source
selector:
entity:
filter:
- device_class: temperature
indoor_temp_upper:
name: Indoor temperature upper limit
description: Target temperature range upper limit
default: 76
selector:
number:
min: 0
max: 100
unit_of_measurement: degree
indoor_temp_lower:
name: Indoor temperature lower limit
description: Target temperature range lower limit
default: 72
selector:
number:
min: 0
max: 100
unit_of_measurement: degree
indoor_humidity_entity:
name: Indoor humidity
description: Indoor humidity source
default: []
selector:
entity:
filter:
- device_class: humidity
indoor_humidity_upper:
name: Indoor humidity upper limit
description: Target humidity range upper limit
default: 65
selector:
number:
min: 0
max: 100
unit_of_measurement: percentage
indoor_co2:
name: Indoor CO2
description: Indoor CO2 source
default: []
selector:
entity:
filter:
- device_class: carbon_dioxide
indoor_co2_level:
name: Indoor CO2 limit
description: Indoor CO2 level that forces air circulation
default: 1000
selector:
number:
min: 0
max: 2000
#############
mode: single
variables:
max_fan_runtime: !input max_fan_runtime
indoor_co2_entity: !input indoor_co2
indoor_co2_entity_is_valid: '{{ indoor_co2_entity is defined and indoor_co2_entity is string and indoor_co2_entity.split(".") | count == 2 }}'
aqi_entity: !input outdoor_aqi
aqi_level: !input outdoor_aqi_level
above_aqi: '{{ states(aqi_entity) | int(0) > 0 and states(aqi_entity) | int(0) > aqi_level | int(0) }}'
indoor_humidity_entity: !input indoor_humidity_entity
indoor_humidity_level: !input indoor_humidity_upper
above_humidity: '{{ states(indoor_humidity_entity) | float(0) > 0 and states(indoor_humidity_entity) | float(0) > indoor_humidity_level | float(0) }}'
outdoor_temp_entity: !input outdoor_temp_entity
indoor_temp_entity: !input indoor_temp
indoor_temp_upper: !input indoor_temp_upper
indoor_temp_lower: !input indoor_temp_lower
temp_start_delta: !input temp_start_delta
temp_stop_delta: !input temp_stop_delta
temp_cool_offset: "{{ states(indoor_temp_entity) | float(0) - temp_start_delta | float(0) if states(indoor_temp_entity) | float(0) > indoor_temp_upper | float(0) else states(indoor_temp_entity) | float(0) - 100 | float(0) }}"
cool_enabled_offset: "{{ temp_cool_offset | float(0) > states(outdoor_temp_entity) | float(0) }}"
cool_enabled: "{{ states(indoor_temp_entity) | float(0) > indoor_temp_upper | float(0) }}"
temp_heat_offset: "{{ states(indoor_temp_entity) | float(0) + temp_start_delta | float(0) if states(indoor_temp_entity) | float(0) < indoor_temp_lower | float(0) else states(indoor_temp_entity) | float(0) + 100 | float(0) }}"
heat_enabled_offset: "{{ temp_heat_offset | float(0) < states(outdoor_temp_entity) | float(0) }}"
heat_enabled: "{{ states(indoor_temp_entity) | float(0) < indoor_temp_lower | float(0) }}"
stop_check_cool_met: "{{ indoor_temp_upper | float(0) + temp_stop_delta | float(0) >= states(indoor_temp_entity) | float(0) }}"
stop_check_cool_delta: "{{ states(outdoor_temp_entity) | float(0) + temp_start_delta | float(0) >= states(indoor_temp_entity) | float(0) }}"
stop_check_heat_met: "{{ indoor_temp_lower | float(0) - temp_stop_delta | float(0) <= states(indoor_temp_entity) | float(0) }}"
stop_check_heat_delta: "{{ states(outdoor_temp_entity) | float(0) - temp_start_delta | float(0) <= states(indoor_temp_entity) | float(0) }}"
trigger_variables:
indoor_temp_upper: !input indoor_temp_upper
indoor_temp_lower: !input indoor_temp_lower
temp_start_delta: !input temp_start_delta
indoor_co2_entity_is_valid: '{{ indoor_co2_entity is defined and indoor_co2_entity is string and indoor_co2_entity.split(".") | count == 2 }}'
##### Triggers #####
trigger:
- alias: TempCoolOn
id: TempOn
#enabled: "{{ cool_enabled }}"
platform: numeric_state
entity_id: !input indoor_temp
above: !input outdoor_temp_entity
value_template: "{{ float(state.state, 0) - temp_start_delta | float(0) if float(state.state, 0) > indoor_temp_upper | float(0) else float(state.state, 0) - 100 | float(0) }}"
#value_template: "{{ float(state.state, 0) - temp_start_delta | float(0) }}"
- alias: TempHeatOn
id: TempOn
#enabled: "{{ heat_enabled }}"
enabled: false
platform: numeric_state
entity_id: !input indoor_temp
below: !input outdoor_temp_entity
value_template: "{{ float(state.state, 0) + temp_start_delta | float(0) if float(state.state, 0) < indoor_temp_lower | float(0) else float(state.state, 0) + 100 | float(0) }}"
#value_template: "{{ float(state.state, 0) + temp_start_delta | float(0) }}"
- alias: 'CO2'
id: 'CO2'
enabled: "{{ indoor_co2_entity_is_valid }}"
platform: numeric_state
entity_id: !input indoor_co2
above: !input indoor_co2_level
# Wait 5 minuetes to prevent a momentary change from triggering
for:
minutes: 5
##### Actions #####
action:
choose:
# If trigger was cool
- conditions: '{{ trigger.alias == ''TempCoolOn'' }}'
alias: 'ActionCoolOn'
sequence:
- service: switch.turn_on
target:
entity_id: !input fan_entity
- wait_for_trigger:
- platform: numeric_state
entity_id:
- !input outdoor_aqi
above: !input outdoor_aqi_level
- platform: numeric_state
entity_id:
- !input indoor_humidity_entity
above: !input indoor_humidity_upper
# Stop if indoor temp - start delta + 0.02 temp goes below outdoor - add 0.02 to keep it from immediately stopping right at the start delta
# IE - 84 inside - 4 + 0.02 < 80 outside
- platform: numeric_state
entity_id:
- !input indoor_temp
below: !input outdoor_temp_entity
value_template: "{{ float(state.state, 0) - temp_start_delta | float(0) + 0.02 | float (0) }}"
# Stop if temp_upper is reached
# IE - 76 inside - 0.02 < 76 upper limit
- platform: numeric_state
entity_id:
- !input indoor_temp
below: !input indoor_temp_upper
value_template: "{{ float(state.state, 0) - 0.02 | float(0) }}"
continue_on_timeout: true
timeout:
hours: "{{ max_fan_runtime | float(0) }}"
minutes: 0
seconds: 0
milliseconds: 0
- service: switch.turn_off
target:
entity_id: !input fan_entity
# If trigger was heat
- conditions: '{{ trigger.alias == ''TempHeatOn'' }}'
alias: 'ActionHeatOn'
sequence:
- service: switch.turn_on
target:
entity_id: !input fan_entity
- wait_for_trigger:
- platform: numeric_state
entity_id:
- !input outdoor_aqi
above: !input outdoor_aqi_level
- platform: numeric_state
entity_id:
- !input indoor_humidity_entity
above: !input indoor_humidity_upper
# Stop if indoor temp + start delta - 0.02 temp goes above outdoor
# IE - 56 inside + 4 - 0.02 > 60 outside
- platform: numeric_state
entity_id:
- !input indoor_temp
above: !input outdoor_temp_entity
value_template: "{{ float(state.state, 0) + temp_start_delta | float(0) - 0.02 | float (0) }}"
# Stop if temp_lower is reached
# IE - 72 inside + 0.02 > 72 upper limit
- platform: numeric_state
entity_id:
- !input indoor_temp
above: !input indoor_temp_lower
value_template: "{{ float(state.state, 0) + 0.02 | float(0) }}"
continue_on_timeout: true
timeout:
hours: "{{ max_fan_runtime | float(0) }}"
minutes: 0
seconds: 0
milliseconds: 0
- service: switch.turn_off
target:
entity_id: !input fan_entity
# If CO2 above limit turn on until below for 1 minute ignoring air quality
# - conditions: '{{ trigger.id == ''CO2'' }}'
# alias: 'CO2High'
# sequence:
# - variables:
# co2_level: !input indoor_co2_level
# - service: switch.turn_on
# target:
# entity_id: !input fan_entity
# - wait_for_trigger:
# - platform: numeric_state
# entity_id:
# - !input indoor_co2
# below: !input indoor_co2_level
# timeout:
# hours: "{{ max_fan_runtime | float(0) }}"
# minutes: 0
# seconds: 0
# milliseconds: 0
# - service: switch.turn_off
# target:
# entity_id: !input fan_entity