A summer automation that tells you, per room, exactly when to close the windows.

The short version, no tech needed: In summer we keep the house cool by opening everything early in the morning and closing up before it gets hot outside. The tricky bit is catching the exact moment to close each room, because outside creeps past inside before you notice. So I taught the house to watch its own temperatures and ping my phone, room by room, the moment I should shut that room’s windows. It does the reverse too: it tells me when it’s finally cooler outside than in, so I know when to open back up and let the cool return.
No new gadgets, just software wiring together things I already run. That’s the whole idea. Everything below is how I built it, and it gets technical, so if you only wanted the gist you can happily stop here.
The problem
In summer we open up the whole house early in the morning to pull in cool air. As we have a house that’s mostly in the shade, we can normally manage fine, even in hot summers. It sometimes becomes a bit uncomfortable, but I still think we don’t need airco. Cooling in the morning normally carries the day.
But there’s a tipping point: once it gets warmer outside than inside, every open window and door is doing the opposite of what you want, letting heat back in. The trick is closing up at exactly the right moment, which is hard to eyeball.
So the goal: a button I flip in the morning that quietly watches the temperatures and pings us on Telegram, per room, the moment we should close that room up.
The existing setup
- Home Assistant for sensors and state, running in Docker (config mapped to a host folder, no named volumes).
- evohome heating integration, which turned out to be the key data source: each zone (
climate.woonkamer,climate.aeron, …) exposes a livecurrent_temperature. - Node-RED for automation logic, already wired to Home Assistant and to a Telegram bot.
- “Fake buttons”, a pattern I already use:
input_booleanhelpers in Home Assistant that exist purely to trigger Node-RED flows. - Grafana + InfluxDB logging Home Assistant data for graphs.
Design decisions
1. Where does the outside temperature come from?
I had no outdoor sensor. Rather than buy hardware, I added a REST sensor pulling Open-Meteo: free, no API key, and you just feed it your lat/long. Because I modelled it as a proper Home Assistant sensor (with device_class: temperature), it also flows into InfluxDB and shows up in Grafana for free. (A nice alternative for the Netherlands is Buienradar, which gives an actual nearby weather-station reading instead of a model blend.)
2. Read outside once, compare against many rooms.
The flow fetches the outside temperature a single time per cycle, then fans out to compare it against each room independently. Cleaner and fewer API calls than one chain per room.
3. Per-room state, so alerts are independent.
Each room has its own “already warned” flag (koeling_gemeld_<room>). You get one Telegram per room, at that room’s own crossover point. Bedrooms cross earlier than the attic.
4. Alert once, with hysteresis.
Fire once when outside ≥ room temp, then stay quiet. Re-arm only when outside drops back at least 0.5 °C below the room again: that half-degree deadband stops it flapping back and forth right at the crossover.
Implementation
Home Assistant: configuration.yaml
The button (a fake-button input_boolean):
input_boolean:
koelen_huis:
name: Cooling the house
icon: mdi:snowflake
initial: offThe outside-temperature sensor:
sensor:
- platform: rest
name: Buiten temperatuur
unique_id: buiten_temperatuur_openmeteo
resource: "https://api.open-meteo.com/v1/forecast?latitude=51.7310&longitude=5.5307¤t=temperature_2m"
method: GET
value_template: "{{ value_json.current.temperature_2m }}"
unit_of_measurement: "°C"
device_class: temperature
scan_interval: 300That gives sensor.buiten_temperatuur, updating every 5 minutes.
Node-RED: the flow
One tab, “Koelen huis”, with two entry points and a shared comparison stage:
[Button on/off] --on--> [Reset flags + "cooling on" msg] --> Telegram "started"
| \-> (immediate check) -+
\-off-> [Reset flags] --> Telegram "stopped" |
v
[Poll every 5 min, only while button on] -----------------> [Get outside temp] -+
v
+-------------- fan out to 5 rooms ----------------+
v
[woonkamer] [aeron] [elias] [steph_en_iris] [zolder]
\--------------+--------------------/
v
[Compare: outside >= room?] --> [Telegram alert]
Key node choices:
- The poll node polls the button (
input_boolean.koelen_huis) every 300 s and only passes through when it’son, so the whole comparison chain is naturally gated by the button with no extra logic. - Each room node (
api-current-stateonclimate.<room>) tags the message withmsg.roomkeyandmsg.roomlabel, so a single comparison function and a single Telegram node handle all five rooms. - The glue that lets one function serve every room: the Get outside temp node stashes its reading in
msg.buiten, and each room’sapi-current-statenode writes its state intomsg.data(rather than overwriting the whole message). So both the outside reading and that room’s own temperature ride the samemsginto the compare function.
The comparison function (runs once per room):
const buiten = parseFloat(msg.buiten);
let binnen = NaN;
try { binnen = parseFloat(msg.data.attributes.current_temperature); } catch (e) {}
const label = msg.roomlabel || 'een kamer';
const key = 'koeling_gemeld_' + (msg.roomkey || 'onbekend');
if (isNaN(buiten) || isNaN(binnen)) {
node.status({ fill: 'red', shape: 'ring', text: label + ': temp onbekend' });
return null;
}
node.status({ fill: 'blue', shape: 'dot', text: `${msg.roomkey}: buiten ${buiten} / binnen ${binnen}` });
const marge = 0.5; // hysteresis
let gemeld = flow.get(key) || false;
if (!gemeld && buiten >= binnen) {
flow.set(key, true);
msg.payload = `🔴 Sluit ${label}! Het is buiten nu ${buiten}°C en in ${label} ${binnen}°C. Doe ramen en deuren dicht om de koelte binnen te houden.`;
return msg;
}
if (gemeld && buiten <= binnen - marge) {
flow.set(key, false); // cooler again -> re-arm
}
return null;The five rooms wired in: climate.woonkamer (living room), climate.aeron, climate.elias, climate.steph_en_iris, and climate.zolder (attic).
Two gotchas worth mentioning
-
The
server-state-changednode (v6) doesn’t use a plainentityIdfield. My first deploy threwConfigError: An entity is required. The v6 node wants the entity inside anentitiesobject:{ entity: ["input_boolean.koelen_huis"], substring: [], regex: [] }. Easy to miss when hand-editingflows.json. -
Sending dynamic text through the Telegram node. I wanted the live temperatures in the message. Reading the node’s source (
node-red-contrib-telegrambot-home) showed the logic:var message = node.staticMessage || msg.payload;So the trick is to leave the node’s message field empty and set
msg.payloadin the function, and the node falls back to the payload.
Verifying it works
Because it’s all file-based (no volumes), the workflow was: edit files → validate → restart the container.
-
Validated the Home Assistant config before restarting:
docker exec homeassistant python -m homeassistant --script check_config -c /config -
After restart, confirmed the new entities actually populated by reading the Home Assistant recorder database: outside came back at 23.6 °C,
climate.woonkamerat 24.5 °C, the buttonoff. -
Checked Node-RED’s log for a clean
Started flowswith no errors after it.
Bonus: it’s all in Grafana
Because the outside temperature is a real Home Assistant sensor, it lands in InfluxDB and can be graphed next to the room temperatures. This panel is real data from 1 July 2026. The green line is the living room (evohome), the orange line is the outside temperature (Open-Meteo):

You can read the whole logic off the graph: outside is cool overnight (~13 °C), climbs through the morning, and around 14:20 crosses above the living room: the first “close the house” alert. It dips just under again, then crosses a second time at 16:10 (this is exactly the hysteresis re-arm behaviour, happening for real), peaks near 26 °C, and finally drops back below in the evening. The two red dashed lines are where Node-RED would have fired a Telegram.
The mirror image: when to open up
I built the same thing in reverse. A second comparison, same fan-out, same per-room hysteresis, just flipped: when it drops cooler outside than a given room, I get a Telegram saying I can open that room’s windows to pull the cool back in. Same “alert once, re-arm on a half-degree deadband” logic, only with the comparison the other way around. Between the two, the house tells me exactly when to close up as it heats and when to open up again as it cools, room by room.
The result
Flip “Cooling the house” in the morning and forget about it. As the day heats up you get a Telegram per room (living room, Aeron, Elias, Steph & Iris, and the attic), each naming the room and both temperatures, at that room’s exact crossover, once. And since the outside temperature is a real Home Assistant sensor, it’s all graphed in Grafana too.
Built with Home Assistant + Node-RED + a free Open-Meteo REST sensor + Telegram. No extra hardware.