Home Assistant Lights That Respect Humans
Presence lighting with lux thresholds, hysteresis, and manual override logic so your smart lights behave properly.
- Intermediate
- Home Assistant
- Presence
- Lighting
- Automation

🎥 Prefer video? Watch the full walkthrough here:
In a previous video I shared an idea to show what’s possible with presence automations. It was more of a concept demo than a deep dive into the logic.
If you haven’t seen that yet, start there.
👇 Watch the original walkthrough
That video shows the behaviour in action.
This article explains how it actually works under the hood.
After that video went live, a few people asked for two things. Especially @Mr_nah over on YouTube:
- Can you show the helpers and the “dark enough” logic?
- Can we pause automations when someone manually changes the light?
So that’s exactly what we’re building here.
For this guide I’ll use the lounge as the example room. Everything can be adapted to any space by swapping entity names.
I’ll also be publishing a full build walkthrough video for this article. The link will live at the bottom once it’s up.
Disclaimer: This is the method I have used. It may not be the best or correct depending on who you ask :-)
Why Smart Lights Still Feel Dumb
At first, your automations feel clever… until you actually start living with them.
You dim the lights to settle in for a movie and thirty seconds later they jump back to bright white.
The kids set the lounge to red and it quietly snaps back to whatever the automation thinks is correct.
You walk into the lounge during the day and the lights flash on even though you can see perfectly fine.
Technically, nothing is broken. Everything is working as designed.
But it does not feel intelligent. It feels reactive.
That difference matters.
Why This Actually Matters
When lighting behaves poorly, you stop trusting it.
You start reaching for the switch. You disable automations. You tell yourself smart lighting “just isn’t worth it.”
The problem isn’t automation.
The problem is automation without context.
When lights understand darkness properly, respect time of day, and back off when you take control, they disappear into the background.
That’s the goal.
Not clever. Not flashy. Simply Invisible.
There is a big difference between lights that react… and lights that behave.
What we’re building here is lighting that understands context. Time of day. Actual darkness. And most importantly, human intent.
What We’re Actually Building
We are not building “motion lighting.”
We are building lighting that:
- Knows what time of day it is
- Understands what “dark enough” actually means
- Does not flicker around thresholds
- Gives humans veto power
- Resumes gracefully
There are three stages to get there:
- Define what dark enough actually means
- Combine presence with that definition
- Give humans control without breaking the system
Let’s start with the foundation.
What You’ll Need
Before we get into templates and helpers, let’s talk about the physical side of this.
You need two core signals:
- Presence – Is someone actually in the room?
- Lux – Is it genuinely dark enough to justify turning lights on?
Without both of those, this whole system falls apart.
Presence without lux means lights turn on in broad daylight. Lux without presence means lights react to clouds instead of people.
You need both.
Note: I think it goes without saying that you will also need a controllable light source 😊
Presence Sensor
I’m using Apollo Automation mmWave sensors, specifically the MSR-2.
They’ve been rock solid for me and include mmWave presence detection, Built-in lux, Temperature, Pressure, and UV sensors.
Which opens up all sorts of additional logic options later if you want to get clever.
I’m not affiliated and I get nothing from this.They’re just genuinely excellent sensors from a friendly team.
If you’re interested, check them out here:
👉 https://apolloautomation.com/collections/sensors
That said, this logic works with any presence sensor. PIR, Zigbee, Wi-Fi, ESPHome, whatever you’re running. Mileage may vary depending on how stable your sensor is.
Your presence sensor needs to expose its occupancy.
In my case that’s: binary_sensor.lounge_presence_ld2450_presence
Yours will be different.
Lux Sensor
You also need to expose a lux value.
In my case that’s: sensor.lounge_presence_ltr390_light
Again, yours will be different. If your sensor doesn’t have built-in lux, you can use a separate light sensor.
The key is simple: We need a reliable lux number to compare against our thresholds.
Once you’ve got presence and lux available in Home Assistant, we can start building intelligence around them.
Step 1: Define Time Context
Before we talk about lux, we need context. Light that feels right at 7am feels harsh at 9pm.
Instead of using one static threshold, we create time “slots” that the rest of the system references.
Time of Day Slot Sensor
This sensor decides whether it’s morning, day, evening, or night.
Use the controls to download, copy or inspect the source YAML.
You can create this as a Template Sensor in the UI:
Settings → Devices & Services → Helpers → Create Helper → Template → Template Sensor
Copy only the Jinja inside the state: block from the YAML below.
Or keep it in YAML
Lighting Scene Slot
This takes it one step further. Instead of hardcoding brightness into your automation, we output scene-friendly slots such as: overnight, early_morning, morning, daytime, evening, or late_evening.
This keeps your motion automation clean. It just says “apply the current scene slot” rather than embedding brightness logic everywhere.
You can create this the same way as above in the UI:
Settings → Devices & Services → Helpers → Create Helper → Template → Template Sensor
And copy only the Jinja inside the state: block from the YAML below.
Or use YAML:
Use the controls to download, copy or inspect the source YAML.
Step 2: Define What “Dark Enough” Means
Why a Single Lux Number Does Not Work
As mentioned earlier, if you use one lux threshold for the entire day, your lighting will always feel slightly off. Slightly too bright. Slightly too eager.
Instead of one number, we will use multiple thresholds based on time of day.
That gives us nuance.
The Lux Threshold Helpers We Need
Create four number helpers in the UI:
Settings → Devices & Services → Helpers → Create Helper → Number
| Name | Entity | Purpose |
|---|---|---|
| Lux Lounge Morning | input_number.lux_lounge_morning |
Lux threshold for morning |
| Lux Lounge Day | input_number.lux_lounge_day |
Lux threshold for daytime |
| Lux Lounge Evening | input_number.lux_lounge_evening |
Lux threshold for evening |
| Lux Lounge Night | input_number.lux_lounge_night |
Lux threshold for night |
These allow you to tune brightness expectations throughout the day without editing YAML.
You can absolutely create these in YAML if that’s your preference. The UI works perfectly fine though.
Below are my settings. Note: Your lux numbers will vary wildly depending on the sensor. I have different lux sensors vary at the same time, same place from 1000 - 2000lx, whereas the lux sensor on my Apollo R Pro-1 is currently sitting at 71.3lx. You just need to ensure your helpers are calibrated to your sensor.
Hysteresis
Hysteresis is what stops your house from overreacting.
Without it, if lux hovers around your threshold, lights can flick on and off repeatedly.
Hysteresis adds a margin so lights only turn on when it is clearly dark, and only turn off when it is clearly bright again.
So, let’s create one more helper:
| Name | Entity | Purpose |
|---|---|---|
| Lux Hysteresis Margin | input_number.lux_hysteresis_margin |
Buffer to prevent flicker |
Active Lux Threshold Sensor
Now we create a template sensor that decides which threshold applies right now. This keeps the motion automation clean. It only ever checks one value.
If using the UI, paste only the Jinja from the state blocks (Make sure you set unit of measure to lx)
Or use YAML:
Use the controls to download, copy or inspect the source YAML.
Note: Change the time ranges and entity IDs to match your setup.
You will now have: sensor.lounge_lux_threshold_active
That sensor automatically outputs the correct lux threshold for the current time of day.
If you named your helpers differently, update the entity IDs inside the template.
If your time ranges are different, adjust the clock values.
The motion automation now only needs to check one clean sensor instead of managing time logic itself.
The “Dark Enough” Binary Sensor
Now we create the binary sensor that actually answers the question:
Is it dark enough to justify turning the lights on?
To do this, we compare actual lux against the active threshold and apply hysteresis.
Go to: Settings → Devices & Services → Helpers → Create Helper
Choose: Template → Template Binary Sensor
| Name | Entity | Purpose |
|---|---|---|
| Lounge Dark Enough | binary_sensor.lounge_dark_enough |
Is it dark enough to turn lights on |
This gives us: binary_sensor.lounge_dark_enough
True means “worth turning the lights on.”
If creating from UI, paste only the Jinja from the state blocks.
Or use YAML:
Make sure you replace: sensor.lounge_presence_ltr390_light with your actual lux sensor entity.
What this does:
- It turns on when lux drops below
threshold - margin - It stays on until lux rises above
threshold + margin
That buffer zone is the hysteresis.
Without it, your lights can flap on and off every time lux hovers around the threshold.
With it, the system feels calm and confident instead of twitchy.
OK we now have a clean binary sensor:
binary_sensor.lounge_dark_enough
That becomes the gatekeeper for your motion automation.
Now we have a stable definition of darkness.
Step 3: Combine Presence with Darkness
Presence sensors are fast. Humans are not.
If you turn lights off the moment presence drops false, the room feels twitchy.
Instead we introduce a vacancy timer inside the automation so lights only turn off after a short buffer.
Why We Use a Vacancy Timer
If you turn lights off the instant presence goes false, your room will feel twitchy.
We all know the feeling, when someone steps slightly out of presence range and then everything goes dark.
Instead:
- Presence detected → cancel timer
- Presence cleared → start timer
- Timer finishes → turn lights off
This makes the room feel calm instead of reactive.
Let’s create a Timer helper:
Go to: Settings → Devices & Services → Helpers → Create Helper
Choose: Timer
Name: Lounge Vacancy Timer
Entity ID: timer.lounge_vacancy
Duration: 00:05:00 (adjust to taste)
Five minutes is a good starting point for a lounge.
For hallways you might use one minute.
For bedrooms, maybe longer.
How “Time of Day” Drives the Lighting
Before we dive into the automation, it is important to understand the logic behind this.
Once you’ve got a sensor.lighting_scene_slot, you can stop hardcoding “turn on the light to X”.
Instead, the automation can ask a much more human question:
“What kind of lighting should this room have right now?”
That’s what the scene slot gives you.
It outputs a simple text value like:
early_morningmorningdaytimeeveninglate_eveningovernight
Then inside the motion automation, we use a choose: block to map each slot to a scene.
So in the morning you might prefer something warmer and softer, during the day you might not want lights at all, and late evening might be a dim “don’t wake the house” scene.
This is the part that makes it feel less like a motion sensor and more like the room has manners.
The Pattern (small example)
Here’s the basic idea. Your full YAML below contains the complete list.
# Choose a scene based on your time-of-day slot sensor
- choose:
- conditions:
- condition: state
entity_id: sensor.lighting_scene_slot
state: early_morning
sequence:
- service: scene.turn_on
target:
entity_id: scene.lights_lounge_early_morning
- conditions:
- condition: state
entity_id: sensor.lighting_scene_slot
state: morning
sequence:
- service: scene.turn_on
target:
entity_id: scene.lights_lounge_morning
Step 4 – Build the Lounge Scenes
Up to this point we’ve defined when the lights should turn on.
Now we define how they should look.
The automation does not set brightness or colour directly.
Instead, it calls a scene based on the value of: sensor.lighting_scene_slot
This keeps logic and lighting design separate.
The automation decides if.
The scene decides what it feels like.
That separation matters.
Create These Exact Scene Names
Go to:
Settings → Automations & Scenes → Scenes → Create Scene
Create the following scenes exactly as named below:
| Time Slot State | Scene Entity Required |
|---|---|
| early_morning | scene.lights_lounge_early_morning |
| morning | scene.lights_lounge_morning |
| daytime | scene.lights_lounge_daytime |
| evening | scene.lights_lounge_evening |
| late_evening | scene.lights_lounge_late_evening |
| overnight | scene.lights_lounge_overnight |
⚠ The entity IDs must match what the automation references.
If you change the names, update the automation YAML accordingly.
What Should Each Scene Look Like?
There is no “correct” setting.
But here is a practical guide:
- early_morning → very soft, low brightness
- morning → comfortable neutral white
- daytime → bright and practical
- evening → warm and relaxed
- late_evening → dim, warm, lower brightness
- overnight → very low or night-light only
Tune these to your home.
The automation will simply ask:
“What time-of-day slot are we in?”
Then run the matching scene. That’s it.
No brightness logic inside the automation.
No colour temperature spaghetti.
Just clean separation of intent.
Motion Automation
Now we combine everything. This is the automation that ties presence and “dark enough” together for the lounge.
Use the controls to download, copy or inspect the source YAML.
If:
- Room is occupied
- It is dark enough
Then turn on the light.
You will also have the timer logic for turning lights off when the room is empty.
At this point, your lighting already feels significantly better than basic motion setups.
But we still haven’t solved the “kids set it to red” problem.
Step 5 – Give Humans Veto Power
Up to this point, the system is behaving “intelligently”.
Now we give humans the final say.
If someone manually changes brightness or colour, the automation should pause.
Not argue. Not “correct” them. Just back off.
To do that, we create a simple gatekeeper.
Create the Manual Override Helper
When this is on, motion automation should do nothing.
Go to: Settings → Devices & Services → Helpers → Create Helper
Choose: Toggle
Name: Lounge Manual Override
Entity ID: input_boolean.lounge_manual_override
When this helper is ON, the motion automation will do nothing.
That’s it. It becomes a simple on/off gate in front of the automation.
Manual Override Detection Automation
Creating this automation detects meaningful “Human intent” with manual changes (colour, brightness, on/off) and sets the manual override flag.
Create a new automation:
Use the controls to download, copy or inspect the source YAML.
Now when someone sets the light to red, the system pauses. No arguments.
To wrap it all up, you can go back into your **motion automation ** and add the below condition (It has been remarked out in the full automation above):
Use the controls to download, copy or inspect the source YAML.
- Room is occupied
- It is dark enough
- Manual override is OFF
Then turn on the light.
Why I Didn’t Rely Only on Adaptive Lighting’s Built-In Takeover
Adaptive Lighting includes a take_over_control option designed to pause adaptation when a light is manually adjusted.
In theory, when you change brightness or colour, Adaptive Lighting should mark that light as manually controlled and stop adjusting it.
In practice, during testing, I found that this behaviour is not always consistent.
Even with:
take_over_control: true
Adaptive Lighting continued to adjust the light after I manually changed brightness.
After reviewing the GitHub issues, there are recurring themes around this behaviour:
https://github.com/basnijholt/adaptive-lighting/issues
The key nuance appears to be this:
Not all “manual” changes are treated as true takeover events.
If the brightness or colour change is still issued through Home Assistant, for example:
- From the dashboard
- From a scene
- From another automation
- From a voice assistant integrated via Home Assistant
The integration may not always classify that change as a takeover.
From a user perspective, this can feel like the light is “fighting you”.
Why the Template-Based Manual Override Is More Reliable
Instead of relying solely on the integration’s internal takeover logic, the template-based override:
- Detects meaningful state changes
- Ignores Adaptive Lighting micro-adjustments
- Explicitly sets a manual override flag
- Blocks further automation until cleared
This makes the behaviour deterministic.
You are not depending on the integration to decide whether a change is manual.
You are explicitly telling your automation stack to stop adjusting the light.
For simple setups, the built-in takeover may work perfectly.
If you want predictable, “respect humans” behaviour in a real household, explicit override logic gives you tighter control.
Smart homes should adapt to people.
Not argue with them.
Resume Script
So we have finished with our red-themed light dance party and want to resume normal lighting and automations again.
We simply create a script that turns off the override helper:
Use the controls to download, copy or inspect the source YAML.
That is all it does.
It does not force lights on.
It does not reset scenes.
It simply clears input_boolean.lounge_manual_override.
When that flag turns off, the motion automation is allowed to operate again.
How You Can Trigger the Resume Script
There are multiple clean ways to call this script:
• From a dashboard button
• From another automation after a timeout
• From a physical button
• From Apple Home, Google Home, etc.
Choose whatever fits your household.
The important part is that resuming automation becomes intentional, not automatic.
Yes we can create an automation that when the input_boolean.lounge_manual_override is enabled, it starts a countdown, afterwhich it clears input_boolean.lounge_manual_override. But this is already long enough 😴
The point is that you decide when control returns.
Apple Home and Google Home
If I’ve intentionally overridden the lighting, then I should also intentionally resume it.
In my mind, the automation should not silently take control back without me knowing.
So, I handle this inside Apple Home.
Exposing the Script to Apple Home
The resume script is just another entity in Home Assistant.
To make it available in Apple Home:
- Go to Settings → Devices & Services → HomeKit
- Edit your HomeKit bridge configuration
- Ensure Scripts are included in the entities exposed
- Select
script.resume_lounge_lights(or whatever you named it) - Reload or restart the HomeKit integration if required
If you are using Exclude mode, you simply need to allow the script domain.
If you are using Include mode, you must:
- Allow the
scriptdomain - Explicitly add the resume script entity (for example
script.resume_lounge_lights)
Once exposed, reload the HomeKit integration if required.
The script will now appear inside Apple Home. By default, it shows up in the “Default Room.”
In my case, that room was called Home (Must have changed with one of their updates). Depending on your HomeKit setup, yours may differ.
I moved my entity to the Lounge room inside Apple Home.
I suggest you now rename it to something meaningful like Reset Lounge Lights or Resume Lounge Lights.
Now it behaves like a scene. That means you can say:
“Hey Siri, resume/reset lounge lights.”
And the override flag is cleared.
No special Voice Assistant setup is required beyond normal HomeKit exposure. If Home Assistant is already connected to Apple Home, this is simply exposing one more entity.
You overrode the system and you decide when it resumes.
Google Home
Google Home works the same way conceptually (I am not using my Google home for automations anymore).
What This System Now Does
- Lights only turn on when it is genuinely dark
- Threshold adapts by time of day
- Hysteresis prevents flicker
- Presence feels stable
- Manual colour or brightness changes pause automation
- Resume restores normal behaviour
No snapping back.
No fighting.
No rude behaviour.
Just lighting that behaves.
Full Build Guide Video
I’ve published a full step-by-step build walkthrough for this exact setup.
👉
If you build this in another room, just swap “lounge” for whatever space you’re working on.
And if your lights stop arguing with you… you’ve done it right.

