June 22, 2026
This Homey flow sends a notification to close the windows when it is cold outside and a window has been open for more than 30 minutes. The other way around, during a summer heatwave, it alerts you to close windows immediately if the outdoor temperature exceeds the indoor temperature, ensuring rooms get some fresh air and the house stays cool for as long as possible.
This Homey flow sends a critical notification to everyone at home to close their windows, and I've made it room-aware. This means that the specific temperatures in individual rooms are included in the calculation.
I've a Honeywell room thermostat installed in every room. These are connected to my Honeyell Evohome system and linked to my Homey using the Honeywell Home evohome App. This allows me to get the temperature per room.
I also connected my Ventus W830 weather station to my Homey using a webhook. The outdoortemp variable is updated whenever the outside temperature changes. This makes my flow event-driven, and tracking the live temperatures changes consumes very little CPU and RAM on my Homey.
The HomeyScript I developed monitors our home's windows based on room-specific temperatures and outdoor conditions. I added a check to ensure I don't receive any notifications during the night.
The script checks if any windows have been left open for longer than 30 minutes; if they have, it compares the indoor and outdoor temperatures to determine if it is either too hot or too cold outside.
It generates a notification to inform everyone at home which windows should be closed. This prevents the rooms from cooling down too much during autumn and winter, or from heating up unnecessarily in the summer.
I've set up an Advanced Flow that uses a variable called windowTempWarningSent. The flow is triggered whenever the outdoortemp variable changes, which then executes a HomeyScript to perform the necessary temperature checks.
If the script returns true, the flow checks if windowTempWarningSent is set to "No." If it is, it sends a critical push notification to everyone at home, using the text defined in the tag generated by the HomeyScript.
The flow uses three triggers. I build an event-driven approach instead of running constant timers. This ensures minimal CPU usage on my Homey and is real-time.
A temperature check is triggered whenever the outdoor temperature changes.
To handle resetting the windowTempWarningSent variable, I’ve created a trigger for each window that activates as soon as the contact alarm turns off. This runs the script to verify if the conditions are still met, preventing immediate repeat notifications from other rooms. Once all conditions are satisfied, the variable is reset to "No."
Additionally, for days when I'm busy working from home and stuck in a Teams meeting, I have included a scheduled reset of the windowTempWarningSent variable at fixed times. This ensures that I receive a follow-up reminder if I didn't close the windows.
To get everything up and running, first create a new HomeyScript named window-fresh-air-check.js and paste the code provided below.
Before you start, you will need to update the WINDOW_MONITOR_LIST variable in the script to match the specific IDs of your own windows and Honeywell sensors. You can retrieve them by running the following helper script:
const devices = Object.values(await Homey.devices.getDevices());
const zones = Object.values(await Homey.zones.getZones());
console.log('--- LIST WITH FIBARO DOOR/WINDOW SENSOR NAMES AND IDS ---')
for (const device of devices) {
if (device && device.driverId === 'homey:app:com.fibaro:FGDW-002') {
const zone = zones.find(z => z.id === device.zone);
console.log(` '${device.name} (${zone.name})': '${device.id}',`);
}
}
console.log('--- LIST WITH HONEYWELL THERMOSTAT NAMES AND IDS ---')
for (const device of devices {
if (device && device.driverId === 'homey:app:com.honeywell:zone') {
const zone = zones.find(z => z.id === device.zone);
console.log(` '${device.name}': '${device.id}',`);
}
}
Here is the complete script for the window-fresh-air-check.js:
// I do not want to receive push notifications at night.
const currentHour = new Date().getHours();
if (currentHour >= 22 || currentHour < 8) {
return false;
}
const WINDOW_MONITOR_LIST = {
'Bedroom': { windowId: 'a1b2c3d4-e5f6-', honeywellId: '8c9d-0123456789ab-' },
'Kitchen': { windowId: '8f280777-64ad-', honeywellId: '8d24-414db0f83a96-' },
};
const allVariables = await Homey.logic.getVariables();
const outdoortempVar = Object.values(allVariables).find(v => v.name === 'outdoortemp');
const COMFORT_LIMIT = 21; // Inside temperature threshold for heating season
const TIME_LIMIT_MINUTES = 30; // How long a window is allowed to stay open
async function getRoomTemperature(zoneName) {
try {
const device = await Homey.devices.getDevice({ id: WINDOW_MONITOR_LIST[zoneName].honeywellId });
return device.capabilitiesObj.measure_temperature.value;
} catch (error) {
console.log(`Error: Could not read sensor ${honeywellId}`);
return null;
}
}
async function getoutdoortempVar() {
try {
return (await Homey.logic.getVariable({ id: outdoortempVar.id })).value;
} catch (error) {
console.log(`Error: Could not read variabele outdoortemp`);
return null;
}
}
// ==========================================
// MAIN SCRIPT
// ==========================================
const outdoor = await getoutdoortempVar();
const openTooLongWindows = [];
const currentTime = new Date();
for (const [roomName, item] of Object.entries(WINDOW_MONITOR_LIST)) {
try {
const windowDevice = await Homey.devices.getDevice({ id: item.windowId });
const isOpen = windowDevice.capabilitiesObj.alarm_contact.value;
if (isOpen) {
const lastUpdatedTime = new Date(windowDevice.capabilitiesObj.alarm_contact.lastUpdated);
const minutesOpen = (currentTime - lastUpdatedTime) / 1000 / 60;
if (minutesOpen >= TIME_LIMIT_MINUTES) {
const room_temperature = await getRoomTemperature(roomName);
if (typeof room_temperature !== 'number') continue;
if (outdoor > room_temperature) {
// Situation 1: Heatwave
openTooLongWindows.push({ name: roomName, reason: 'hitte' });
}
else if (outdoor < room_temperature && room_temperature < COMFORT_LIMIT) {
// Situation 2: Cold weather
openTooLongWindows.push({ name: roomName, reason: 'kou' });
}
}
}
} catch (error) {
console.log(`Error: Failed to process monitoring for room: ${roomName}.`);
}
}
if (openTooLongWindows.length > 0) {
// Helper function to create nice Dutch sentences
const formatList = arr => {
if (arr.length === 1) return arr[0];
if (arr.length === 2) return `${arr[0]} en ${arr[1]}`;
return `${arr.slice(0, -1).join(', ')} en ${arr.slice(-1)}`;
};
const allRoomNames = openTooLongWindows.map(w => w.name);
const openMsg = allRoomNames.length === 1
? `Please note, the window in the room ${allRoomNames[0]} is still open.`
: `Please note, the windows in the rooms ${formatList(allRoomNames)} are still open.`;
const heatRooms = openTooLongWindows.filter(w => w.reason === 'hitte').map(w => w.name);
const coldRooms = openTooLongWindows.filter(w => w.reason === 'kou').map(w => w.name);
const advice = [];
if (heatRooms.length) {
advice.push(heatRooms.length === 1
? `It's outside ${outdoor}°C and it gets warmer, close the window.`
: `It's outside ${outdoor}°C and it gets warmer, close the windows.`
);
}
if (coldRooms.length) {
advice.push(coldRooms.length === 1
? `The window has been open for ${TIME_LIMIT_MINUTES} minutes and it is outside ${outdoor}°C.`
: `The windows have been open for ${TIME_LIMIT_MINUTES} minutes, and it is outside ${outdoor}°C.`
);
}
const finalNotificationMessage = `${openMsg} ${advice.join(' ')}`;
await tag("notificationMessage", finalNotificationMessage);
return true;
}
return false; // Flow stops, no spam