Guides

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:

  1. Define what dark enough actually means
  2. Combine presence with that definition
  3. 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:

  1. Presence – Is someone actually in the room?
  2. 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.

Template: Time of Day Slot

Use the controls to download, copy or inspect the source YAML.

Download 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:

Template: Lighting Scene Slot

Use the controls to download, copy or inspect the source YAML.

Download 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.

Number: Lux threshold

Use the controls to download, copy or inspect the source YAML.

Download YAML

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
Number: Hysteresis

Use the controls to download, copy or inspect the source YAML.

Download YAML

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:

Lux Threshold (Active) Logic

Use the controls to download, copy or inspect the source YAML.

Download 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:

Dark Enough Logic

Use the controls to download, copy or inspect the source YAML.

Download 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_morning
  • morning
  • daytime
  • evening
  • late_evening
  • overnight

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.

Motion Automation (Lounge)

Use the controls to download, copy or inspect the source YAML.

Download 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:

Manual Override Detection

Use the controls to download, copy or inspect the source YAML.

Download 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):

Add Manual Override Condition

Use the controls to download, copy or inspect the source YAML.

Download YAML
Now if:
  • 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:

Resume Automations Script (Lounge)

Use the controls to download, copy or inspect the source YAML.

Download 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:

  1. Go to Settings → Devices & Services → HomeKit
  2. Edit your HomeKit bridge configuration
  3. Ensure Scripts are included in the entities exposed
  4. Select script.resume_lounge_lights (or whatever you named it)
  5. 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 script domain
  • 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.