Skip to content

Commit eb67bdc

Browse files
committed
post: add HVAC Helper Sensor
Additional changes: - update HVAC overhaul post to point to new post - add automating-hvac-system draft
1 parent caa309e commit eb67bdc

19 files changed

+828
-0
lines changed

_drafts/automating-hvac-system.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
title: Automating the HVAC System
3+
# date: 2025-07-16
4+
# categories are Family, Photography, Places, Projects, Reviews, Software, Thoughts
5+
category: Projects
6+
tags:
7+
- homeassistant
8+
- automation
9+
- smarthome
10+
- climate
11+
media_subpath: /assets/img/posts/automating-hvac-system/
12+
image:
13+
path: preview.jpg
14+
lqip: preview.lqip.jpg
15+
---
16+
17+
In the [last post]({% post_url 2025-07-16-hvac-helper-sensors %}), we created a bunch of sensors
18+
and automations to make it possible to control the HVAC system. This time, we're going to build the
19+
portions of the system which control the thermostat, raising and lowering the temperature as
20+
desired.
21+
22+
This is the weirdest one and has a separate script to accompany the automation. To describe the goal,
23+
we're trying to determine where the the warmest (when AC is running) room is and set the cooling
24+
to run until _that_ room is at the target temperature. This has the unfortunate side effect of
25+
cooling the rest of the rooms beyond the target, but I'm fine with that (since my office is the
26+
hottest room in the house so I get to benefit from it). However, because wee want to make sure that
27+
we're getting to the actual desired temperature and because of the way thermostats work, we have to
28+
tell it to cool _beyond_ the point where it thinks it needs to be.
29+
30+
The result is this... well _long script_. A lot of the length is simply documentation for the inputs
31+
to the script, but there's more complex stuff in there too
32+
33+
```yaml
34+
{% raw %}
35+
script:
36+
update_thermostat_temp:
37+
alias: Climate - Update Thermostat Temperature
38+
mode: restart
39+
description: >-
40+
This script handles checking the current temperatures, the desired temperature,
41+
and the HVAC mode (all passed into this script) and then instructs the
42+
HVAC system to turn on/off accordingly by setting the target temperature
43+
of the thermostat in charge.
44+
45+
This script expects to work with a single thermostat; if you have multiple
46+
thermostats, set up an automation for each.
47+
48+
This script only anticipates working with an enabled HVAC system, meaning
49+
in "cool" or "heat" mode. Currently, it does not work with "heat_cool".
50+
If the HVAC system is in "off" mode, then do not call this script. It also
51+
does not pay any attention to away mode; that should be handled separately,
52+
perhaps as a condition in the automation.
53+
54+
fields:
55+
buffer:
56+
name: Buffer
57+
description: >-
58+
A buffer applied to the temperature when reading which helps prevent
59+
rapid cycling
60+
example: "0.5"
61+
default: 0.5
62+
selector:
63+
number:
64+
min: 0
65+
max: 2
66+
step: 0.1
67+
mode: box
68+
69+
overshoot:
70+
name: Overshoot
71+
description: >-
72+
This will be added/subtracted to the target temperature in order to
73+
ensure the thermostat stays engaged until next iteration of this script
74+
example: "2"
75+
default: 2
76+
selector:
77+
number:
78+
min: 0
79+
max: 3
80+
step: 0.1
81+
mode: box
82+
83+
thermostat_temp_sensor:
84+
name: Thermostat Temperature Sensor
85+
description: >-
86+
The temperature the thermostat currently thinks it is. This can often
87+
be different than the temperature in multiple rooms, yet controls the
88+
real-world behavior of the system, so we need to include it
89+
example: "sensor.my_thermostat_temperature"
90+
required: true
91+
selector:
92+
entity:
93+
filter:
94+
- device_class: temperature
95+
domain: sensor
96+
97+
thermostat:
98+
name: Thermostat
99+
description: The thermostat which should be controlled by this script
100+
example: "climate.my_thermostat"
101+
required: true
102+
selector:
103+
entity:
104+
filter:
105+
- domain: climate
106+
107+
room_temp_sensors:
108+
name: Room Temperature Sensors
109+
description: >-
110+
A collection of room temperatures which should be considered when
111+
calculating whether to engage or disengage the HVAC system. These are
112+
likely to be temp sensors in the rooms you want covered.
113+
example: "['sensor.office_temperature', 'sensor.bedroom_temperature']"
114+
required: true
115+
selector:
116+
entity:
117+
filter:
118+
- device_class: temperature
119+
domain: sensor
120+
multiple: true
121+
122+
room_target_temp_entities:
123+
name: Room Target Temperature Entities
124+
description: >-
125+
A collection of entities which contain the target temperatures for
126+
each of the rooms. This can be a single desired temp (e.g. "cool to 78")
127+
or it can be a list (e.g. "cool this room to 75, that room to 78")
128+
example: "['input_number.office_target_temperature', 'input_number.bedroom_target_temperature']"
129+
required: true
130+
selector:
131+
entity:
132+
filter:
133+
- domain: input_number
134+
multiple: true
135+
136+
current_hvac_mode:
137+
name: Current HVAC Mode
138+
description: >-
139+
The mode which the HVAC system is currently in. Accepted values are
140+
"cool" and "heat".
141+
example: cool
142+
required: true
143+
selector:
144+
select:
145+
options:
146+
- cool
147+
- heat
148+
149+
variables:
150+
buffer: "{{ buffer | default(0.5) | float }}"
151+
overshoot: "{{ overshoot | default(2) | float }}"
152+
153+
# add the thermostat temp sensor to the list of room temp sensors
154+
all_temp_sensors: >
155+
{{ room_temp_sensors + [thermostat_temp_sensor] }}
156+
157+
# Gather the temperatures for all the sensors
158+
all_current_temps: >
159+
{{ all_temp_sensors | map('states') | select('match', '^[0-9.]+$') | map('int') | list }}
160+
161+
# Gather all the target temperatures
162+
all_target_temps: >
163+
{{ room_target_temp_entities | map('states') | select('match', '^[0-9.]+$') | map('int') | list }}
164+
165+
# We'll also need the thermostat current temp for future use
166+
current_thermostat_temp: "{{ states(thermostat_temp_sensor) | int }}"
167+
168+
# Determine the temp to use as the current temp; max temp if cooling, min temp
169+
# for heating. More description: if we're heating, we want to make the
170+
# coldest room the current temp so that can be heated to the desired temp.
171+
# For cooling, we want to cool the hottest room to the target temp
172+
current_aggregate_temp: >-
173+
{% if current_hvac_mode == "cool" %}
174+
{{ all_current_temps | max }}
175+
{% else %}
176+
{{ all_current_temps | min }}
177+
{% endif %}
178+
179+
# Determine the temp to use as the target temp; min temp if cooling, max temp
180+
# for heating.
181+
current_target_temp: >-
182+
{% if current_hvac_mode == "cool" %}
183+
{{ all_target_temps | min }}
184+
{% else %}
185+
{{ all_target_temps | max }}
186+
{% endif %}
187+
188+
sequence:
189+
- alias: Decide whether we should engage the HVAC and set the target temp
190+
if:
191+
- alias: Test to see if we're in cooling mode
192+
condition: template
193+
value_template: >-
194+
{{ current_hvac_mode == "cool" }}
195+
then:
196+
- alias: Set the variables for cooling
197+
variables:
198+
should_engage: >-
199+
{{ current_aggregate_temp > current_target_temp + buffer }}
200+
target_engaged_temp: >-
201+
{{ current_target_temp - overshoot }}
202+
target_disengaged_temp: >-
203+
{{ current_thermostat_temp + overshoot }}
204+
else:
205+
- alias: Set the variables for heating
206+
variables:
207+
should_engage: >-
208+
{{ current_aggregate_temp < current_target_temp - buffer }}
209+
target_engaged_temp: >-
210+
{{ current_target_temp + overshoot }}
211+
target_disengaged_temp: >-
212+
{{ current_thermostat_temp - overshoot }}
213+
214+
- alias: Choose the target temp as appropriate based on mode and temp
215+
if:
216+
- alias: Test to see if we need to engage the thermostat
217+
condition: template
218+
value_template: >-
219+
{{ should_engage }}
220+
then:
221+
- alias: Use the engage values for the target
222+
variables:
223+
actual_target_temp: "{{ target_engaged_temp }}"
224+
else:
225+
- alias: Use the disengaged value for the target
226+
variables:
227+
actual_target_temp: "{{ target_disengaged_temp }}"
228+
229+
- alias: Check to see if the HVAC system is disabled; don't continue if it's not
230+
condition: state
231+
entity_id: binary_sensor.disable_upstairs_climate_operation
232+
state: "off"
233+
234+
- alias: Check to see if the thermostat is on; don't continue if it's not
235+
condition: template
236+
value_template: >-
237+
{{ is_state(thermostat, "heat") or is_state(thermostat, "cool") }}
238+
239+
- alias: Update the thermostat's temperature
240+
action: climate.set_temperature
241+
target:
242+
entity_id: >-
243+
{{ thermostat }}
244+
data:
245+
temperature: "{{ actual_target_temp }}"
246+
{% endraw %}
247+
```

_posts/2025-07-02-hvac-overhaul-in-home-assistant.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,13 @@ Thus, we'll piece all of this together, bit by bit. This post got away from me a
117117
plan to do these in later posts; setting up what sensors we need, what helpers, and how to link
118118
everything together to make a fully integrated climate system.... 85% of which we could probably
119119
do natively in Flair and Ecobee apps, but where's the fun in that?
120+
121+
The way I see it, there's four major phases to
122+
this project, and I'll cover each in its own post:
123+
124+
1. **[Helpers and automation prep]({% post_url 2025-07-16-hvac-helper-sensors %})** in which we prep all the sensors we'll need to do the subsequent steps
125+
2. **HVAC Automation** which takes the sensors and directly controls the thermostat temperature/mode
126+
3. **Flair Automation** which adds onto the HVAC automation by controlling where the air flows
127+
4. **Additional Stuff** such as guest mode, away mode, etc., things which I want but aren't needed for daily operation (meaning I will likely ignore them forever)
128+
129+
So hang on and I'll publish posts as each step gets sorted!

0 commit comments

Comments
 (0)