A motion sensor sees movement and then forgets you the moment you sit still, which is why motion lighting drops the lights on someone reading on the sofa. This recipe uses an mmWave presence sensor instead. It reports the room occupied for as long as anyone is in it, so the lights stay on through a still evening and go off shortly after the last person leaves.
The clear delay is a short grace period after the room reads empty, set in seconds. A few seconds smooths over the brief gaps a sensor can report as someone crosses the room, without leaving the lights on for long once the room is genuinely empty. Everything runs on your own sensor and lights, with no cloud in the loop.
Only after dark
A well-lit room in the afternoon does not need the lights. Pick a light sensor as the optional illuminance role and the lights only come on while the room is below the darkness level you set. Leave the sensor out and the lights follow presence at any hour.
A softer light at night
Add dimmable lights as the optional dimmer role and they come up at the accent brightness you set rather than at full, so walking into a room late does not mean the lights at their brightest.
Files Selora AI's pipeline reads to install this recipe. The manifest declares roles, inputs, and integrations; the package files are Jinja-templated YAML applied to Home Assistant.
# Presence Room Lighting — hold a room's lights on while an mmWave presence
# sensor sees anyone, and switch them off shortly after the room empties.
#
# One required presence role (multi-select, several sensors can cover a room)
# and one required light role. Optional illuminance gates the lights to
# darkness; optional dimmable lights come up at a softer accent brightness.
slug: presence-room-lighting
version: 1.0.0
title: Presence Room Lighting
tagline: The lights stay on while anyone is in the room and go off shortly after it empties.
description: >-
Keeps the lights on for as long as an mmWave presence sensor sees someone in
the room, including someone sitting still, and switches them off after a
short grace period once the room is empty. Add a light sensor to run only
after dark and dimmable lights that come up softly.
author: Selora AI
released: "2026-08-28"
min_integration_version: "0.12.0"
tags: [lighting, presence, mmwave, occupancy]
roles:
# Presence sensors — multi-select so several mmWave sensors can cover one
# room. binary_sensor + device_class occupancy is how they register.
# min_count 1: with no sensor there is nothing to follow.
- id: presence_sensors
title: Presence sensors
kind: binary_sensor
device_class: occupancy
min_count: 1
selection: required
description: >-
The mmWave presence sensors covering the room. Any one seeing presence
keeps the lights on.
# Lights — multi-select. min_count 1: the recipe needs a light to control.
- id: lights
title: Room lights
kind: light
min_count: 1
selection: required
description: >-
The light or lights the recipe switches with presence.
# Optional illuminance sensor to gate the lights to darkness. max_count 1.
- id: illuminance
title: Light level sensor
kind: sensor
device_class: illuminance
min_count: 0
max_count: 1
selection: required
description: >-
Optional. A light sensor so the lights only come on while the room is
dark. Leave empty to follow presence at any hour.
# Optional dimmable accent lights brought up at a softer brightness.
- id: dimmers
title: Dimmable accent lights
kind: light
min_count: 0
selection: required
description: >-
Optional. Dimmable lights the recipe brings up at the accent brightness
instead of full. Leave empty to skip.
inputs:
# Grace period, in seconds, after the room reads empty before the lights go
# off. A few seconds smooths over brief detection gaps without leaving the
# lights on once the room is genuinely empty.
- id: clear_seconds
type: number
label: Clear delay (seconds)
description: >-
How long the room must read empty before the lights turn off.
default: 30
min: 0
max: 3600
# Darkness threshold in lux, used only when an illuminance sensor is picked.
- id: dark_lux
type: number
label: Darkness threshold (lux)
description: >-
With a light sensor picked, the lights only come on below this reading.
default: 40
min: 1
max: 1000
# Brightness percent for the optional dimmable accent lights.
- id: dim_brightness
type: number
label: Accent brightness (percent)
description: >-
Brightness the optional dimmable lights come up at.
default: 40
min: 1
max: 100
package_files:
- package/automations/presence.yaml.j2
{# Presence Room Lighting: one automation to switch the lights on when the
room becomes occupied, one to switch them off after it has been empty for
the clear delay.
The on automation fires on any presence sensor going on, optionally gated to
a dark illuminance reading. The off automation waits for every presence
sensor to have read empty for the clear delay, then confirms all are clear
before switching off, so one sensor still seeing someone keeps the lights
on. Optional dimmable lights come up at the accent brightness. #}
automation:
- id: selora_recipe_{{ slug | replace('-', '_') }}_on
alias: "Presence Room Lighting: on"
description: >-
When any presence sensor reports the room occupied, switch the lights on,
optionally only while the room is dark.
mode: restart
trigger:
{% for sensor in roles.presence_sensors %}
- platform: state
entity_id: {{ sensor }}
to: "on"
{% endfor %}
{% if roles.illuminance %}
condition:
- condition: numeric_state
entity_id: {{ roles.illuminance[0] }}
below: {{ inputs.dark_lux }}
{% endif %}
action:
- service: light.turn_on
target:
entity_id:
{% for light in roles.lights %}
- {{ light }}
{% endfor %}
{% if roles.dimmers %}
- service: light.turn_on
target:
entity_id:
{% for dimmer in roles.dimmers %}
- {{ dimmer }}
{% endfor %}
data:
brightness_pct: {{ inputs.dim_brightness }}
{% endif %}
- id: selora_recipe_{{ slug | replace('-', '_') }}_off
alias: "Presence Room Lighting: off"
description: >-
Once every presence sensor has read the room empty for the clear delay,
switch the lights off.
mode: restart
trigger:
{% for sensor in roles.presence_sensors %}
- platform: state
entity_id: {{ sensor }}
to: "off"
for:
seconds: {{ inputs.clear_seconds }}
{% endfor %}
condition:
# for: applies to every entity in the list, so all of them must have read
# empty for the full clear delay. Without it the trigger firing for one
# sensor was enough and the rest only had to be off at that moment, which
# cut the delay short whenever a room had more than one sensor.
- condition: state
state: "off"
for:
seconds: {{ inputs.clear_seconds }}
entity_id:
{% for sensor in roles.presence_sensors %}
- {{ sensor }}
{% endfor %}
action:
- service: light.turn_off
target:
entity_id:
{% for light in roles.lights %}
- {{ light }}
{% endfor %}
{% for dimmer in roles.dimmers %}
- {{ dimmer }}
{% endfor %}