Thank you for your thoughts, this sounds like something I should try. Will do this afap and come back with the results! 
@rlkoshak To be honst I don’t know how the way my rules are written is called. I started this many years ago when it was the only way i knew and ported it since then over all upcoming versions of OH. Here is an example. In general I tried to split rules in scripts. So nearly all rules are calling one ore many scripts interally. This was my try to keep things more abstract.
I also try to save results of calculations in items to react on their changed
event in another rules. So there may be some chains with 3 or 4 iterations of those chains. Can this be also be a problem?
check_for_idle_rooms.rules
:
rule "Run check_for_idle_rooms"
when
Time cron "0 0/1 * * * ?" // Every minute
then
callScript("check_for_idle_rooms")
end
check_for_idle_rooms.script
:
logInfo("script", "check_for_idle_rooms");
val currentHour = now.getHour();
val shortThresholdInMinutes = 5;
val thresholdInMinutes = 30;
val nowInMilliseconds = new DateTimeType().getZonedDateTime().toInstant.toEpochMilli;
val nowInSeconds = nowInMilliseconds / 1000;
val nowInMinutes = nowInSeconds / 60;
//
// Bathroom
//
val lastMotionBathroomInMilliseconds = (Virtual_Bathroom_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionBathroomInSeconds = lastMotionBathroomInMilliseconds / 1000;
val lastMotionBathroomInMinutes = lastMotionBathroomInSeconds / 60;
var lastMotionBathroomDifferenceInMinutes = nowInMinutes - lastMotionBathroomInMinutes;
sendCommand(Virtual_Bathroom_LastMotion_WholeRoom_IdleTime, lastMotionBathroomDifferenceInMinutes);
logInfo("check_for_idle_rooms", "bathroom idle minutes: " + lastMotionBathroomDifferenceInMinutes);
if (
lastMotionBathroomDifferenceInMinutes != NULL &&
lastMotionBathroomDifferenceInMinutes > thresholdInMinutes
) {
if (Virtual_Bathroom_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_Bathroom_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_Bathroom_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_Bathroom_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// Bedroom
//
val lastMotionBedroomInMilliseconds = (Virtual_Bedroom_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionBedroomInSeconds = lastMotionBedroomInMilliseconds / 1000;
val lastMotionBedroomInMinutes = lastMotionBedroomInSeconds / 60;
var lastMotionBedroomDifferenceInMinutes = nowInMinutes - lastMotionBedroomInMinutes;
var bedroomThresholdInMinutes = thresholdInMinutes;
if (currentHour >= 0 && currentHour <= 6) {
bedroomThresholdInMinutes = shortThresholdInMinutes;
}
sendCommand(Virtual_Bedroom_LastMotion_WholeRoom_IdleTime, lastMotionBedroomDifferenceInMinutes);
logInfo("check_for_idle_rooms", "bedroom idle minutes: " + lastMotionBedroomDifferenceInMinutes);
if (
lastMotionBedroomDifferenceInMinutes != NULL &&
lastMotionBedroomDifferenceInMinutes > thresholdInMinutes
) {
if (Virtual_Bedroom_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_Bedroom_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_Bedroom_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_Bedroom_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// Dining room
//
val lastMotionDiningRoomInMilliseconds = (Virtual_DiningRoom_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionDiningRoomInSeconds = lastMotionDiningRoomInMilliseconds / 1000;
val lastMotionDiningRoomInMinutes = lastMotionDiningRoomInSeconds / 60;
var lastMotionDiningRoomDifferenceInMinutes = nowInMinutes - lastMotionDiningRoomInMinutes;
sendCommand(Virtual_DiningRoom_LastMotion_WholeRoom_IdleTime, lastMotionDiningRoomDifferenceInMinutes);
logInfo("check_for_idle_rooms", "dining room idle minutes: " + lastMotionDiningRoomDifferenceInMinutes);
if (
lastMotionDiningRoomDifferenceInMinutes != NULL &&
lastMotionDiningRoomDifferenceInMinutes > thresholdInMinutes
) {
if (Virtual_DiningRoom_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_DiningRoom_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_DiningRoom_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_DiningRoom_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// GuestBathroom
//
val lastMotionGuestBathroomInMilliseconds = (Virtual_GuestBathroom_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionGuestBathroomInSeconds = lastMotionGuestBathroomInMilliseconds / 1000;
val lastMotionGuestBathroomInMinutes = lastMotionGuestBathroomInSeconds / 60;
var lastMotionGuestBathroomDifferenceInMinutes = nowInMinutes - lastMotionGuestBathroomInMinutes;
sendCommand(Virtual_GuestBathroom_LastMotion_WholeRoom_IdleTime, lastMotionGuestBathroomDifferenceInMinutes);
logInfo("check_for_idle_rooms", "guest bathroom idle minutes: " + lastMotionGuestBathroomDifferenceInMinutes);
if (
lastMotionGuestBathroomDifferenceInMinutes != NULL &&
lastMotionGuestBathroomDifferenceInMinutes > thresholdInMinutes
) {
if (Virtual_GuestBathroom_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_GuestBathroom_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_GuestBathroom_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_GuestBathroom_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// Hallway
//
val lastMotionHallwayInMilliseconds = (Virtual_Hallway_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionHallwayInSeconds = lastMotionHallwayInMilliseconds / 1000;
val lastMotionHallwayInMinutes = lastMotionHallwayInSeconds / 60;
var lastMotionHallwayDifferenceInMinutes = nowInMinutes - lastMotionHallwayInMinutes;
sendCommand(Virtual_Hallway_LastMotion_WholeRoom_IdleTime, lastMotionHallwayDifferenceInMinutes);
logInfo("check_for_idle_rooms", "hallway idle minutes: " + lastMotionHallwayDifferenceInMinutes);
if (
lastMotionHallwayDifferenceInMinutes != NULL &&
lastMotionHallwayDifferenceInMinutes > shortThresholdInMinutes
) {
if (Virtual_Hallway_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_Hallway_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_Hallway_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_Hallway_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// Kitchen
//
val lastMotionKitchenInMilliseconds = (Virtual_Kitchen_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionKitchenInSeconds = lastMotionKitchenInMilliseconds / 1000;
val lastMotionKitchenInMinutes = lastMotionKitchenInSeconds / 60;
var lastMotionKitchenDifferenceInMinutes = nowInMinutes - lastMotionKitchenInMinutes;
sendCommand(Virtual_Kitchen_LastMotion_WholeRoom_IdleTime, lastMotionKitchenDifferenceInMinutes);
logInfo("check_for_idle_rooms", "kitchen idle minutes: " + lastMotionKitchenDifferenceInMinutes);
if (
lastMotionKitchenDifferenceInMinutes != NULL &&
lastMotionKitchenDifferenceInMinutes > thresholdInMinutes
) {
if (Virtual_Kitchen_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_Kitchen_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_Kitchen_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_Kitchen_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// Living room
//
val lastMotionLivingRoomInMilliseconds = (Virtual_LivingRoom_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionLivingRoomInSeconds = lastMotionLivingRoomInMilliseconds / 1000;
val lastMotionLivingRoomInMinutes = lastMotionLivingRoomInSeconds / 60;
var lastMotionLivingRoomDifferenceInMinutes = nowInMinutes - lastMotionLivingRoomInMinutes;
sendCommand(Virtual_LivingRoom_LastMotion_WholeRoom_IdleTime, lastMotionLivingRoomDifferenceInMinutes);
logInfo("check_for_idle_rooms", "living room idle minutes: " + lastMotionLivingRoomDifferenceInMinutes);
if (
lastMotionLivingRoomDifferenceInMinutes != NULL &&
lastMotionLivingRoomDifferenceInMinutes > thresholdInMinutes
) {
if (
Philips_LivingRoom_TV_OnWall_Power.state == OFF &&
Virtual_LivingRoom_LastMotion_WholeRoom_IsIdle.state != ON
) {
sendCommand(Virtual_LivingRoom_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_LivingRoom_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_LivingRoom_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// Office
//
val lastMotionOfficeInMilliseconds = (Virtual_Office_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionOfficeInSeconds = lastMotionOfficeInMilliseconds / 1000;
val lastMotionOfficeInMinutes = lastMotionOfficeInSeconds / 60;
var lastMotionOfficeDifferenceInMinutes = nowInMinutes - lastMotionOfficeInMinutes;
sendCommand(Virtual_Office_LastMotion_WholeRoom_IdleTime, lastMotionOfficeDifferenceInMinutes);
logInfo("check_for_idle_rooms", "office idle minutes: " + lastMotionOfficeDifferenceInMinutes);
if (
lastMotionOfficeDifferenceInMinutes != NULL &&
lastMotionOfficeDifferenceInMinutes > thresholdInMinutes
) {
if (Virtual_Office_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_Office_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_Office_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_Office_LastMotion_WholeRoom_IsIdle, OFF)
}
}
//
// Pantry
//
val lastMotionPantryInMilliseconds = (Virtual_Pantry_LastMotion_WholeRoom_DateTime.state as DateTimeType).getZonedDateTime().toInstant.toEpochMilli;
val lastMotionPantryInSeconds = lastMotionPantryInMilliseconds / 1000;
val lastMotionPantryInMinutes = lastMotionPantryInSeconds / 60;
var lastMotionPantryDifferenceInMinutes = nowInMinutes - lastMotionPantryInMinutes;
sendCommand(Virtual_Pantry_LastMotion_WholeRoom_IdleTime, lastMotionPantryDifferenceInMinutes);
logInfo("check_for_idle_rooms", "pantry idle minutes: " + lastMotionPantryDifferenceInMinutes);
if (
lastMotionPantryDifferenceInMinutes != NULL &&
lastMotionPantryDifferenceInMinutes > thresholdInMinutes
) {
if (Virtual_Pantry_LastMotion_WholeRoom_IsIdle.state != ON) {
sendCommand(Virtual_Pantry_LastMotion_WholeRoom_IsIdle, ON)
}
} else {
if (Virtual_Pantry_LastMotion_WholeRoom_IsIdle.state != OFF) {
sendCommand(Virtual_Pantry_LastMotion_WholeRoom_IsIdle, OFF)
}
}
For example somewhere else I listen on Virtual_Bathroom_LastMotion_WholeRoom_IsIdle
again to turn off lights:
rule "Virtual_Bathroom_LastMotion_WholeRoom_IsIdle"
when
Item Virtual_Bathroom_LastMotion_WholeRoom_IsIdle received command ON
then
if (
Hue_Virtual_LightZone_Bathroom_Power.state == ON &&
Virtual_EveryWhere_Switch_Automation_Light.state == ON &&
Virtual_Everywhere_LastActiveRoom_Room_Name.state != "bathroom"
) {
logInfo("Virtual_Bathroom_LastMotion_WholeRoom_IsIdle", "bathroom: turning off lights since idle threshold reached");
sendCommand(Hue_Virtual_LightZone_Bathroom_Power, OFF)
}
end