From a48020c98568e6004d680c86795b893055450b94 Mon Sep 17 00:00:00 2001 From: Bohdan Bahno Date: Sun, 26 Jul 2026 10:56:43 +0100 Subject: [PATCH] Add window rooms template --- Occupancy_Lighting_with_Manual_Override.yaml | 6 +- ...ing_with_Manual_Override_Window_Rooms.yaml | 176 ++++++++++++++++++ 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 Occupancy_Lighting_with_Manual_Override_Window_Rooms.yaml diff --git a/Occupancy_Lighting_with_Manual_Override.yaml b/Occupancy_Lighting_with_Manual_Override.yaml index 9041f80..5e5055d 100644 --- a/Occupancy_Lighting_with_Manual_Override.yaml +++ b/Occupancy_Lighting_with_Manual_Override.yaml @@ -87,7 +87,11 @@ action: occupied: "{{ is_state(occupancy_entity, 'on') }}" - choose: - # Case 1: Manual physical switch flipped + # Case 1: Manual physical switch flipped (either direction). + # The Shelly's edge input mode has already flipped the relay locally - + # HA must NOT issue a light.turn_on/off here, or it will fight the + # person's manual action. We only start the override timer so that + # occupancy/lux logic stays out of the way for the configured window. - conditions: - condition: template value_template: "{{ trigger.id == 'manual_switch' }}" diff --git a/Occupancy_Lighting_with_Manual_Override_Window_Rooms.yaml b/Occupancy_Lighting_with_Manual_Override_Window_Rooms.yaml new file mode 100644 index 0000000..e6ae59d --- /dev/null +++ b/Occupancy_Lighting_with_Manual_Override_Window_Rooms.yaml @@ -0,0 +1,176 @@ +blueprint: + name: Occupancy Lighting with Manual Override (Window Rooms) + description: > + Turns lights on when an occupancy group sensor indicates presence AND + the room is dark enough (lux below threshold). Turns lights off when + the group becomes inactive for the configured delay. + + A physical switch toggle suppresses sensor-based automation for a + configurable period using a timer helper. This automation never + commands the light directly in response to the switch - it assumes + your switch/relay (e.g. a Shelly in edge/detached input mode) already + controls the light locally and instantly, independent of Home + Assistant. HA only watches for the switch event to start the override + window, so it never fights a manual on/off. + + Create a 'Binary Sensor Group' helper for your room's occupancy + sensors, a 'Sensor Group' helper (mean or min) for your room's lux + sensors if you have more than one, and one timer helper per room. + domain: automation + input: + light_target: + name: Light + selector: + target: + entity: + domain: light + + occupancy_group: + name: Occupancy Sensor / Group + description: Select a single binary sensor or a Binary Sensor Group helper containing this room's sensors. + selector: + entity: + domain: binary_sensor + + illuminance_sensor: + name: Illuminance Sensor / Group + description: Select a lux sensor (e.g. Apollo R-Pro1 LTR390) or a Sensor Group helper averaging multiple lux sensors in this room. + selector: + entity: + domain: sensor + device_class: illuminance + + illuminance_threshold: + name: Darkness threshold (lux) + description: Lights will only turn on for occupancy if measured lux is below this value. Typical indoor daylight near a window is 300-1000+ lux; a dim room is under 50 lux. Tune to taste. + default: 50 + selector: + number: + min: 0 + max: 2000 + step: 1 + mode: box + unit_of_measurement: lux + + switch_input: + name: Physical switch input + description: > + Select the switch/relay's INPUT entity (e.g. a Shelly's input + binary_sensor reporting the physical toggle), NOT the light entity + itself and not a mirror of the light's on/off state. If this is + wired to something that also changes state whenever this + automation calls light.turn_on/off, you'll get a feedback loop + where the automation's own actions look like manual switch events. + selector: + entity: + domain: binary_sensor + + override_timer: + name: Override timer + selector: + entity: + domain: timer + + override_duration: + name: Override duration (minutes) + default: 30 + selector: + number: + min: 1 + max: 1440 + step: 1 + mode: box + unit_of_measurement: minutes + + off_delay: + name: Off delay (seconds) + default: 5 + selector: + number: + min: 0 + max: 300 + step: 1 + mode: box + unit_of_measurement: seconds + +mode: restart + +variables: + occupancy_entity: !input occupancy_group + illuminance_entity: !input illuminance_sensor + lux_threshold: !input illuminance_threshold + timer_entity: !input override_timer + duration_minutes: !input override_duration + +trigger: + - id: occupancy_change + platform: state + entity_id: !input occupancy_group + + - id: manual_switch + platform: state + entity_id: !input switch_input + + - id: timer_finished + platform: event + event_type: timer.finished + event_data: + entity_id: !input override_timer + + # Re-evaluate if it gets dark while someone is already sitting in the room + - id: illuminance_change + platform: state + entity_id: !input illuminance_sensor + for: + seconds: 10 + +action: + - variables: + override_active: "{{ is_state(timer_entity, 'active') }}" + occupied: "{{ is_state(occupancy_entity, 'on') }}" + is_dark: "{{ states(illuminance_entity) | float(9999) < lux_threshold }}" + + - choose: + # Case 1: Manual physical switch flipped (either direction). + # The Shelly's edge input mode has already flipped the relay locally - + # HA must NOT issue a light.turn_on/off here, or it will fight the + # person's manual action. We only start the override timer so that + # occupancy/lux logic stays out of the way for the configured window. + - conditions: + - condition: template + value_template: "{{ trigger.id == 'manual_switch' }}" + sequence: + - action: timer.start + target: + entity_id: !input override_timer + data: + # We multiply by 60 because timer.start data maps natively to HH:MM:SS or raw seconds + duration: "{{ duration_minutes * 60 }}" + + # Case 2: Occupancy or lux changed, but manual override is currently active (Do Nothing) + - conditions: + - condition: template + value_template: "{{ (trigger.id in ['occupancy_change', 'illuminance_change']) and override_active }}" + sequence: [] + + # Case 3: Occupancy detected, override NOT active, and room is dark enough + - conditions: + - condition: template + value_template: "{{ occupied and is_dark }}" + sequence: + - action: light.turn_on + target: !input light_target + + # Default Case: Not occupied, or occupied-but-bright-enough, or timer finished -> sequence handles turning off + default: + - delay: + seconds: !input off_delay + - condition: template + value_template: > + {{ + not is_state(timer_entity, 'active') + and (not is_state(occupancy_entity, 'on') or states(illuminance_entity) | float(9999) >= lux_threshold) + }} + - action: light.turn_off + target: !input light_target +