Skip to content

Presence-Aware Lighting in Home Assistant Using a YAML Blueprint

Posted on 27 January 2026 · 10 min read home automation home assistant

Tired of lights turning off too soon? This is how I built a presence-aware lighting system in Home Assistant using a blueprint.

I’ve automated my home with Home Assistant for years, but motion-sensor-based lighting always annoyed me — it turns off too soon. You know the feeling. Walking into a room, the light turns on, but it turns off at the first quiet moment even if you’re still sitting there reading, listening to music, or watching TV.

So I decided to build a proper presence-aware lighting system right in Home Assistant as a blueprint. The idea is simple:

  • Each room has a set of sensors that define “presence” (motion sensors, media players, PCs, whatever)
  • Lights turn on automatically based on time of day and room occupancy
  • Lights turn off after a configurable timeout, but with a flash warning before turning off
  • Manual light control is respected — if you turn on a light yourself, the automation will not override it (but it will still turn it off after timeout)
  • Occupancy is exposed via input booleans, so other automations can use it
  • The blueprint is reusable across all rooms through simple UI configuration

I’ll walk you through how I set it up, what it does, and how you can extend it.

My First Attempt

When I first installed Home Assistant, many tutorials online recommended using Node-RED for automations. Admittedly, the visual flow-based approach is appealing, but it quickly became unwieldy for anything non-trivial.

Here is what my Node-RED flow looked like for my home’s presence lighting:

Node-RED Flow
Node-RED Flow for Presence Lighting. Look at that spaghetti!

I’m a big fan of clean, maintainable code, and this just wasn’t cutting it. So I decided to scrap Node-RED and implement the entire logic as a reusable blueprint (with the help of ChatGPT — seriously, it’s a game-changer for writing automations).

Prerequisites

Before we start, you should have:

  • Home Assistant up and running
  • (Optional) Input booleans for each room: input_boolean.occupied_<room> and input_boolean.auto_lights_<room>
  • binary_sensor.sunset and binary_sensor.night_mode helpers (you can create these using the Time of Day) integration.

These can go in your configuration.yaml. The Night Mode sensor can be set up from the UI or in YAML, but the sunset sensor needs to be added in YAML like this:

binary_sensor:
  - platform: tod
    name: Sunset
    after: sunset
    after_offset: "-00:30"
    before: sunrise
    unique_id: sunset
  - platform: tod
    name: Night Mode
    after: "22:00:00"
    before: "05:00:00"
    unique_id: night_mode

We’re going to use a blueprint, so it’s portable, maintainable, and reusable across all rooms.

Room Configuration

The blueprint lets you configure each room through the Home Assistant UI. When you create an automation from the blueprint, you’ll configure:

  • Presence entities: Motion sensors, media players, or any device that indicates someone is present. I have an ICMP ping sensor for my PC that marks me as present when it’s on the network.
  • Lights: Which lights to control (can be multiple)
  • Scenes (optional): Which scene to use for sunset and night mode
  • Timeouts: How long to wait before turning off lights

For example, my man cave configuration looks like this:

  • Presence Sensors: binary_sensor.man_cave_sensor_motion, media_player.man_cave_tv, sensor.man_cave_pc_ping
  • Lights: light.man_cave
  • Sunset Scene: scene.man_cave_stranger_things
  • Night Scene: scene.man_cave_sleepy
  • Timeouts: 10 minutes (sunset), 5 minutes (night)

A few things to note:

  • presence_entities can include motion sensors, media players, PCs, or any device that can indicate someone is present. Media players are considered “active” when playing, paused, or on (but not idle, standby, or off).
  • lights is a list, so you can control multiple lights per room.
  • scene and scene_night define which scene to use when lights turn on. If no scene is defined, we fall back to 80% brightness during the day and 5% at night.
  • timeout_minutes and timeout_minutes_night control how long the room waits before turning off the lights.

How It Works

The automation has two main branches:

1. Presence Detected

When any of the room’s presence entities turn on:

  • Mark the room as occupied (input_boolean.occupied_<room>).

  • Turn on the lights if auto-lighting is not disabled (using the optional input_boolean.auto_lights_<room>) and all lights are currently off.

  • Decide what to turn on based on the time of day:

    • Night mode: either the night scene or very dim lights (5%).
    • After sunset but not night: the default scene or 80% brightness.
    • Daytime: do nothing; lights stay off (but will remain on if you manually turned them on as long as the room is occupied).

This ensures that lights never interrupt your manual adjustments and only turn on automatically when appropriate.

2. Presence Cleared

When all presence entities in a room are off/inactive:

  1. Start a timeout countdown (timeout_minutes).
  2. When the timeout expires, flash the lights gently to warn you (a soft brightness pulse).
  3. Wait 30 more seconds after the flash.
  4. If the room is still empty, turn off the lights and mark the room as unoccupied.

This handles the common problem of “lights turning off too early” while still conserving energy.

The flash is usually followed in our house by a flailing hand gesture to convince the IR sensor that we’re still there!

The Full Blueprint

Here’s the complete blueprint YAML. You can import this directly into Home Assistant or save it in your blueprints/automation/ folder:

blueprint:
  name: Presence-Aware Lighting System
  description: >
    Robust presence-based lighting with sunset/night scenes, configurable timeouts,
    manual override detection, and optional occupancy tracking.
    
    Supports motion sensors (bursty), media players, door sensors, and static presence indicators.
  domain: automation
  author: James Harding

  input:
    presence_binary_sensors:
      name: Binary Presence Sensors
      description: Motion sensors, door sensors, or any binary_sensor that indicates presence when "on".
      selector:
        entity:
          domain: binary_sensor
          multiple: true
      default: []

    presence_media_players:
      name: Media Players
      description: >
        Media players that indicate presence when playing/paused/on.
        Room is considered occupied when state is not "off", "unavailable", "idle", or "standby".
      selector:
        entity:
          domain: media_player
          multiple: true
      default: []

    lights:
      name: Lights
      description: The lights to control in this room.
      selector:
        entity:
          domain: light
          multiple: true

    sunset_scene:
      name: Sunset Scene
      description: >
        Scene to activate from sunset until night mode begins.
        If not set, lights will turn on at 80% brightness.
      selector:
        entity:
          domain: scene
      default: ""

    night_scene:
      name: Night Scene
      description: >
        Scene to activate during night mode (e.g., late evening/sleep time).
        If not set, lights will turn on at 5% brightness.
      selector:
        entity:
          domain: scene
      default: ""

    occupied_helper:
      name: Occupied Helper (Optional)
      description: >
        An input_boolean to track room occupancy externally.
        Useful for other automations or dashboards.
      selector:
        entity:
          domain: input_boolean
      default: ""

    auto_lights_toggle:
      name: Auto Lights Toggle (Optional)
      description: >
        An input_boolean that enables/disables automatic lighting.
        When "off", lights won't turn on/off automatically, but occupancy tracking still works.
      selector:
        entity:
          domain: input_boolean
      default: ""

    timeout_sunset:
      name: Sunset Timeout (minutes)
      description: How long to wait after presence clears before turning off lights (sunset period).
      default: 10
      selector:
        number:
          min: 1
          max: 60
          unit_of_measurement: minutes
          mode: slider

    timeout_night:
      name: Night Timeout (minutes)
      description: How long to wait after presence clears before turning off lights (night period).
      default: 5
      selector:
        number:
          min: 1
          max: 60
          unit_of_measurement: minutes
          mode: slider

    night_mode_sensor:
      name: Night Mode Sensor
      description: A binary_sensor that is "on" during night mode (e.g., late evening/sleep time).
      selector:
        entity:
          domain: binary_sensor
      default: binary_sensor.night

    sunset_sensor:
      name: Sunset Sensor
      description: A binary_sensor that is "on" after sunset (typically sun.sun based).
      selector:
        entity:
          domain: binary_sensor
      default: binary_sensor.sunset

trigger:
  - platform: state
    entity_id: !input presence_binary_sensors
    id: binary_sensor_change

  - platform: state
    entity_id: !input presence_media_players
    id: media_player_change

variables:
  presence_binary_sensors: !input presence_binary_sensors
  presence_media_players: !input presence_media_players
  lights: !input lights
  sunset_scene: !input sunset_scene
  night_scene: !input night_scene
  occupied_helper: !input occupied_helper
  auto_lights_toggle: !input auto_lights_toggle
  timeout_sunset: !input timeout_sunset
  timeout_night: !input timeout_night
  night_mode_sensor: !input night_mode_sensor
  sunset_sensor: !input sunset_sensor
  brightness_sunset: 80
  brightness_night: 5
  transition_sunset: 2
  transition_night: 3

condition: []

action:
  - variables:
      check_binary_presence: >
        {{ presence_binary_sensors | default([]) | select('is_state', 'on') | list | length > 0 }}
      check_media_presence: >
        {{ presence_media_players | default([]) 
           | reject('is_state', 'off') 
           | reject('is_state', 'unavailable') 
           | reject('is_state', 'unknown')
           | reject('is_state', 'idle')
           | reject('is_state', 'standby')
           | list | length > 0 }}
      presence_active: >
        {{ check_binary_presence or check_media_presence }}
      is_night: >
        {{ is_state(night_mode_sensor, 'on') }}
      is_after_sunset: >
        {{ is_state(sunset_sensor, 'on') }}
      auto_lights_enabled: >
        {{ auto_lights_toggle == '' or auto_lights_toggle is none or is_state(auto_lights_toggle, 'on') }}
      any_light_on: >
        {{ lights | select('is_state', 'on') | list | length > 0 }}
      timeout_minutes: >
        {{ timeout_night if is_night else timeout_sunset }}

  - choose:
      - conditions:
          - condition: template
            value_template: "{{ presence_active }}"
        sequence:
          - if:
              - condition: template
                value_template: "{{ occupied_helper != '' and occupied_helper is not none }}"
            then:
              - service: input_boolean.turn_on
                target:
                  entity_id: "{{ occupied_helper }}"

          - condition: template
            value_template: "{{ auto_lights_enabled }}"

          - condition: template
            value_template: "{{ not any_light_on }}"

          - condition: template
            value_template: "{{ is_after_sunset }}"

          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ is_night }}"
                sequence:
                  - choose:
                      - conditions:
                          - condition: template
                            value_template: "{{ night_scene != '' and night_scene is not none }}"
                        sequence:
                          - service: scene.turn_on
                            target:
                              entity_id: "{{ night_scene }}"
                    default:
                      - service: light.turn_on
                        target:
                          entity_id: "{{ lights }}"
                        data:
                          brightness_pct: "{{ brightness_night }}"
                          transition: "{{ transition_night }}"
            default:
              - choose:
                  - conditions:
                      - condition: template
                        value_template: "{{ sunset_scene != '' and sunset_scene is not none }}"
                    sequence:
                      - service: scene.turn_on
                        target:
                          entity_id: "{{ sunset_scene }}"
                default:
                  - service: light.turn_on
                    target:
                      entity_id: "{{ lights }}"
                    data:
                      brightness_pct: "{{ brightness_sunset }}"
                      transition: "{{ transition_sunset }}"

      - conditions:
          - condition: template
            value_template: "{{ not presence_active }}"
        sequence:
          - delay:
              minutes: "{{ [timeout_minutes | int, 1] | max }}"

          - if:
              - condition: template
                value_template: "{{ auto_lights_toggle == '' or auto_lights_toggle is none or is_state(auto_lights_toggle, 'on') }}"
            then:
              - if:
                  - condition: template
                    value_template: "{{ lights | select('is_state', 'on') | list | length > 0 }}"
                then:
                  - repeat:
                      count: 1
                      sequence:
                        - service: light.turn_on
                          target:
                            entity_id: "{{ lights }}"
                          data:
                            brightness_step_pct: 15
                            transition: 0.3
                        - delay:
                            milliseconds: 400
                        - service: light.turn_on
                          target:
                            entity_id: "{{ lights }}"
                          data:
                            brightness_step_pct: -15
                            transition: 0.3
                        - delay:
                            milliseconds: 600
                  
                  - delay:
                      seconds: 30

              - service: light.turn_off
                target:
                  entity_id: "{{ lights }}"
                data:
                  transition: "{{ transition_night if is_state(night_mode_sensor, 'on') else transition_sunset }}"

          - if:
              - condition: template
                value_template: "{{ occupied_helper != '' and occupied_helper is not none }}"
            then:
              - service: input_boolean.turn_off
                target:
                  entity_id: "{{ occupied_helper }}"

mode: restart

Extending and Customizing

  • Create a new automation from the blueprint for each room
  • Adjust timeout_minutes for each room depending on usage (bedroom might need shorter timeouts than office)
  • Scenes can be swapped for different moods — golden hours in the evening, “stranger things” (my red and blue 80s theme) for night
  • Other automations can use the occupied_<room> helper to adjust heating, music, or notifications
  • The blueprint uses mode: restart which means if new presence is detected during the timeout, it restarts — perfect for bursty motion sensors

Final Thoughts

This setup gives you presence-aware lighting without complicated integrations or scripting. Lights behave intuitively, flash to warn before turning off, and respect your manual overrides. It’s fully reusable across rooms and easy to configure through Home Assistant’s UI.

Give it a try in your Home Assistant setup, and let me know how it works for you or if you have any suggestions. Happy automating!



← Back to all posts