MoLight — composable virtual entities for occupancy/daylight/schedule-aware lighting (no automations)

Hi all :waving_hand:

I've been building MoLight, a HACS custom integration that lets you get occupancy-, daylight-, and schedule-aware lighting without writing a single automation. Instead of YAML, you wrap your real sensors and lights in virtual building blocks and wire them together entirely from the UI.

The problem it solves

Hand-writing "turn the lights off a while after the room empties" automations starts simple and gets ugly fast once you deal with the real world:

  • motion sensors with different hold timeouts
  • sensors that are great at triggering occupancy but bad at maintaining it (mmWave, I'm looking at you), or vice-versa
  • schedules that mix fixed times and sun events
  • Home Assistant restarts landing in the middle of a countdown
  • a sensor dropping to unavailable at the worst possible moment

MoLight folds all of that into a small set of reusable, composable entities.

The building blocks

Each is its own config entry — create as many as you like:

Entity What it does
Virtual Occupancy Sensor Wraps one motion/presence sensor; estimates when the person actually left
Virtual Combined Occupancy Sensor Merges several occupancy sensors with trigger/maintain roles
Virtual Illuminance Sensor Turns a lux reading into a steady bright/dark signal (with hysteresis)
Virtual Schedule Sensor On inside time windows defined by fixed times and/or sun events
Virtual Light Controls N real lights with an occupancy/illuminance/schedule-aware state machine

Sensors are reusable — one occupancy or illuminance sensor can feed many lights. You use the virtual light in your dashboards and voice assistants instead of the real ones.

Some things it handles that I'm happy with

  • Countdowns anchor to when the person actually left, not the moment a sensor happens to clear — each sensor's own hold time is respected.
  • False-detection filtering — a fly or a heat blip is classified and won't cut short lights you turned on by hand, but it can let auto-lit lights turn off quickly.
  • Manual control is never gated. You can always turn a light on, even when it's bright or outside a schedule window.
  • Follow-mode schedules give porch-light behavior (on at window start, off at window end) while respecting manual overrides mid-window.
  • Optional blink/dim warning before an automatic turn-off, plus a grace period to re-trigger — instead of a room suddenly going dark.
  • Keep-on holds — every virtual light gets a companion Auto-off switch, and any on/off entity (e.g. input_boolean.guest_mode) can suspend automatic turn-offs for guest mode / movie night.
  • Restarts and unavailable sources are handled everywhere — missed schedule boundaries are applied exactly once, sensor blips are never misread as state changes, and a dead motion sensor can't hold the lights on forever.

Everything is local_push, no cloud, no polling — schedule transitions fire on the exact boundary. And all of the behavior above is covered by an automated test suite.

Bulk setup

If you've got a lot of rooms, there are Discover… actions that scan your existing entities and bulk-create virtual wrappers, plus an Assign a sensor to several lights flow that wires one shared sensor into many lights in a single pass.

Install

It's a HACS custom repository:

  1. HACS → add https://github.com/jharris4/molight as a custom repository (type: Integration)
  2. Install MoLight, restart HA
  3. Settings → Devices & Services → Add Integration → MoLight

Everything after that is configured from the UI — no YAML.

Repo / full docs: GitHub - jharris4/molight: Smart Virtual Light Control Entities for Home Assistant · GitHub

This is a fairly new project (currently v1.3.0), so I'd really value feedback, bug reports, and ideas — especially edge cases around weird sensors and restart timing. Happy to answer questions in this thread. :folded_hands:

3 Likes

Since the configuration of MoLight is all UI-driven, here are some examples of the actual field values you'd enter for various scenarios. Every create form starts with a Name (the entity ID derives from it) and ends with an optional Entity ID override if you want to pin it. :folded_hands:


Example 1 — Just a turn-off timer (the simplest case)

No sensors at all — a virtual light that turns its lights off a set time after they come on. Great for a closet, pantry, or garage where you just want "never left on all day." One entry.

Virtual Light

Name:              Pantry                       # → light.pantry
Lights:            light.pantry_real            # pin the Entity ID field if it
Turn-off timeout:  300         # off 5 min after it's turned on

Turn it on (app, voice, or wall switch) → 5 minutes later it turns itself off. That's the whole thing.


Example 2 — Simple occupancy case

One motion sensor, lights off a bit after you leave. Two entries.

1. Virtual Occupancy Sensor — wraps the real motion sensor

Name:                 Hallway Occupancy        # → binary_sensor.hallway_occupancy
Source sensor:        binary_sensor.hallway_motion
Occupancy timeout:    30      # my sensor holds "on" ~30s after last motion
False-detection grace: 5      # a single instantaneous blip = fly/heat, ignored
Clear after unavailable: 60   # if the sensor dies, don't hold lights forever

2. Virtual Light — points at the real light, references the sensor above

Name:              Hallway                     # → light.hallway  (pin the Entity ID
Lights:            light.hallway_real          #    field if it clashes with the real one)
Turn-off timeout:  60         # 60s after you actually left
Occupancy sensor:  binary_sensor.hallway_occupancy

Walk in → lights on. Room empties → 60s countdown anchored to when you actually left (not when the sensor cleared) → off. Turn it on by hand and it still turns off after the room empties, but a false blip never cuts short a manual on.

Note the guard: Turn-off timeout (60) must be ≥ the occupancy timeout (30). MoLight enforces this both ways.


Example 3 — Living room (the whole toolbox)

Motion for triggering, an mmWave sensor to hold while you sit still, only when it's dark, and a warning blink before it drops you into darkness.

1. Virtual Occupancy Sensor — the trigger (regular PIR)

Name:               Living Occupancy           # → binary_sensor.living_occupancy
Source sensor:      binary_sensor.living_motion
Occupancy timeout:  30

2. Virtual Occupancy Sensor — the mmWave, used only to maintain

Name:               Living Presence            # → binary_sensor.living_presence
Source sensor:      binary_sensor.living_mmwave
Occupancy timeout:  120

(mmWave is too twitchy to start occupancy, but perfect for keeping the room lit while you're still.)

3. Virtual Illuminance Sensor

Name:           Living Dark Enough             # → binary_sensor.living_dark_enough
Source sensor:  sensor.living_lux
Threshold:      40
Hysteresis:     10     # bright at 50lx, dark below 30lx — stops flapping

4. Virtual Light

Name:                   Living Room            # → light.living_room
Lights:                 light.living_lamps
Turn-off timeout:       180
Occupancy sensor:       binary_sensor.living_occupancy    # regular = turns on & off
Maintain occupancy:     binary_sensor.living_presence     # only holds an on light on
Illuminance sensor:     binary_sensor.living_dark_enough
Illuminance mode:       control   # dark gates turn-ons AND bright forces off
Auto-on brightness:     60%       # automatic turn-ons come up at 60%; manual left alone
Effect warning duration: 3        # 3s "about to turn off" cue...
Effect brightness:       0%       # ...a blink fully off
Warning grace period:    20       # then 20s at current brightness to re-trigger

Behavior: motion + it's dark → lights on at 60%. Sit still → mmWave keeps them on even after the PIR clears. Leave → countdown starts only once both sensors are clear. Before turning off you get a quick blink, then 20s grace to wave and cancel. Get up in that window and it's as if nothing happened — original brightness restored, no trace.

Use Illuminance mode: gate instead if your lux sensor can see the lights it controls (otherwise they'd oscillate).


Example 4 — Porch light (schedule follow-mode)

On at the later of sunset−15 and 21:00, off at 07:00. The schedule window is just a set of fields on the form — one row for the start edge, one for the end:

1. Virtual Schedule Sensor

Name:          Porch Schedule                  # → binary_sensor.porch_schedule
Start time:    21:00
Start sun:     sunset
Start offset:  -15         # minutes relative to the sun event
Start combine: latest      # whichever of time / sun is later wins
End time:      07:00       # (no sun anchor on this edge)

2. Virtual Light

Name:            Porch                          # → light.porch
Lights:          light.porch_real
Schedule sensor: binary_sensor.porch_schedule
Schedule mode:   follow    # on at window start, off at window end

follow = porch-light behavior; the window owns the light but manual changes mid-window still stand. Use gate instead if you want occupancy to control the light only inside the window.


Global "don't touch my lights" toggle: make an input_boolean.guest_mode and drop it into the Keep-on entities field of any virtual light — while it's on, every automatic turn-off is suspended (timers, forced-offs, window ends), but turn-ons and manual off still work. Every light also gets its own companion ... Auto-off switch for the same thing per-room.

I really like the possibilities with MoLight. It looks like it'll solve a lot of edge cases in lighting automation that I just haven't figured out how best to deal with.

I'm still thinking on how to incorporate door sensors here. But I'm sure liking all the situations you've covered for PIR/radar motion & occupancy, and illuminance, & sun position, & timing / schedules. Nice work!

1 Like

Thanks for the feedback!

I thought about door sensors. I have a couple in my house but don’t use them for lighting automation.

I could see two scenarios:

  • turn on lights when door opens then countdown to off
  • turn on lights when a door opens and leave it on until closed.

Kind of like a motion vs closet mode.

1 Like

I like the idea of handling all the occupancy and lighting logic through reusable virtual entities instead of maintaining lots of automations. The attention to edge cases like restarts, sensor hold times and manual overrides is especially impressive. I'll definitely be keeping an eye on this project.

1 Like

Yep I've got a couple places where I use door sensors for lighting control.

For closets, I should really be using door sensors for both lights-on & off. (vs PIR motion + countdown). I do like having some delay before turn-off, e.g. if someone changes their mind & re-enters the closet.

My main case is door-open event to get an earliest possible lights-on when entering rooms that have normally closed door. And then motion sensors for occupancy, and countdown after unoccupied. Person often closes the door once they're in the room, so door sensor can't tell me anything re when they left that room; just when they entered.

I also have some door-open logic that turns on lights in the area on the other side of the door, e.g., if anyone in the room goes out into an unlit hallway, or leaves an outbuilding at night, to turn on outside lights. Those have proven very useful.

FYI I added support for door sensors for the virtual lights along with a few other features.

2 Likes

Door sensor sounds great; I’ll check it out.

Meanwhile, I’m still missing some part of the overall normal setup. My first “simple” light setup hasn’t been working reliably. Maybe I’ve got too many (or not enough?) config items set up. Still checking it out.

EDIT: I installed v1.4.0, & read the Door sensor docs-- Looks like it should work really well for a couple different cases here. I’ll use Door on my next light automation replacement. Right now, I’m trying to get my first room working completely as expected.

Hmm, can you describe what kind of issue you encountered? Been working great in my testing. Happy to look into it if you can provide more detail.

Sorry about the delayed reply, I’m not getting notifications for this post… If you’re still seeing issues you could open an issue on the GitHub repo.

Yeah, I need to take a very detailed look at it, writing down all components & settings & results & timings. I’ve been meaning to allocate some time to go thru it, & was indeed thinking to submit as GitHub issue. Sometimes it’s exactly what I expected; other times turning off too soon. Could also be affected by my PIR sensor, which has sometimes seemed flaky. Another issue I think I’ve been experiencing, is motion triggers from ceiling fans.

Bottom line, I need to gather better data. And make sure I’ve got complete understanding of all the integration’s “knobs & levers”. Thanks much for what you’re doing.

Sounds good!

I did add a false detections feature because my laundry room seemed to keep having random / occasional phantom triggers. When enabled it turns light back off if they were turned on by motion of a very short period (configurable).

If you get back to looking into this and can share the configuration for the light you’re talking about I could take a look.

1 Like

Hi

I’m trying MoLight to replace Entity Controller (EC still works fine, but it has not been updated for a while, so I’m afraid it will break one day, so I want to be prepared :slight_smile: )

With EC, it was possible to have a “normal mode” and “night mode” on a single entity (mainly for different brightness levels or colors during the night). With MoLight, it looks like I need to create 2 Virtual Schedule Sensors (Day / Night), and create one Virtual Light for each schedule with the proper settings. Is this correct ?

I also want to be able to “override” the auto-off timer by manually using my wall switch. I expected the “auto-off switch” created with each Virtual Light to do this (i.e. turning that switch off would disable the timed auto-off, keeping the list on permanently even with no motion detected). I seems that does not work: even with the auto-off switch turned off, my light still turns off.
Did I misunderstood something?

K.

Hi there, thanks for trying out MoLight!

“normal mode” vs “night mode” are interesting feature ideas, but that’s not what the Virtual Schedule Sensors are currently designed for. Currently, you can configure a Virtual Light to use one Virtual Schedule Sensor to determine when the light turns on/off on a set schedule, or to control when the light responds to motion events to turn on (follow vs gate mode in the Virtual Light Settings). I do see the use case for what you’re asking for though, so I’ll have a think about what I could add to support that the next time I work on the integration code. Can you tell me a bit more about how you’d expect “night mode” vs “normal mode” to work? For example, do those modes tell the lights when to turn on/off, or is it just a way to control the settings of the light when they do turn on during certain times of day?

As for disabling the auto-off timer, that is exactly what the “auto-off” switch created with each Virtual Light is designed to do. If you give me detailed information about how you’re trying to use it and how it’s not working I can try to dig into it further. There are already quite a few automated tests around this feature in the integration’s test suite, but maybe I missed some scenario/use-case that you’re hitting.

The exact scenario that I’m currently using with EC is the following.

I want my bathroom lights to automatically turn and off based on a PIR sensor.
During the day, the brightness will be set to 100%, while at nightime, it will be set to 50% only. In both cases, the light will turn off when no presence is detected anymore.

As my presence sensor does not cover the shower area properly, I use the wall switch to force the light to remain on (EC manages that automatically you manually change the state of the light entities)

EC uses 2 modes for day and night, so I can easily have a dimmed light during the night (in the example below, I only change the brightness, but I can also have specific colors for each period)

eclairage_sdb:
  sensor: binary_sensor.hue_presence_sdb_occupancy
  entities:
    - light.ikea_sdb_miroir_gauche
    - light.ikea_sdb_miroir_milieu
    - light.ikea_sdb_miroir_droite
  state_entities:
    - light.ikea_sdb_miroir_gauche
  delay: 180
  service_data:
    brightness: 100
  night_mode:
    service_data:
      brightness: 50
    start_time: "22:00:00"
    end_time: "07:00:00"

I tried to achieve the same thing with MoLight by creating 2 virtual schedules: one for the daytime (7am → 10pm) and one for nighttime (10pm → 7am).

I then created 2 virtual lights (with the same “actual light entities” for each), each one with the proper schedule sensor assigned, using the “gate” option to scope the presence detection to the selected schedule.

I suppose this configuration causes conflicts. I temporarily disabled one of the 2 virtual lights, and it looks like the auto-off switch works as expected. If both virtual lights are enabled, the behaviour of the auto-off switches is non consistent.

I have not created the wall switch automation yet, but it should be nothing more than turning off the “auto off” switch when needed (and turning it on again when the lights are turned of manually with the wall switch).

I’ll do a few more tests tomorrow.

K.

Thanks for the detailed explanation! Having an alternate set of brightness / colors for when the light turns on based on a schedule shouldn’t be too hard to add to MoLight. I can see the usefulness of it for some of my lights, so that gives me motivation to add the feature! :slight_smile:

The workaround you described where you created two virtual lights for the same light, and a different virtual schedule sensor was a clever trick, but unfortunately the virtual schedule sensor only gates when motion turns on the light, not the auto-off, so yeah the conflict was definitely having two lights. I think you’d need to turn off the “auto-off” switch for both lights to disable that feature…

I’ll see if I can get around to adding “scheduled turn-on settings” option for MoLight Virtual Lights soon, and keep you posted.

PS. J’ai remarqué que vous avez des entity ids en français ; êtes-vous en France ou ailleurs? J’habite à Montréal… :slight_smile:

Thanks.

I’ll keep a single virtual light for the time being, my eyes will survive :slight_smile:

I have two other virtual lights with a single schedule (corridors with lamps that only turn on automatically at night), and it’s working fine.

Good job! Everything could be achieved manually with automations, scripts and such, but it’s so easier like this!

Yes, I’m from France!

K.

I just released a new version 1.6.0 of MoLight, with a new feature you’ll be interested in!

There’s now the option to create a “Virtual Scheduled Light”. It lets you choose a schedule sensor, and have all the settings of the virtual light change depending on whether it is inside or outside of the schedule window.

This should let you change the turn on brightness of the light based on the time of days (or any other setting too!)

Try it out when you have the time, and let me know if it meets all your needs.

Merci pour cette excellente suggestion de fonctionnalité!

1 Like

Merci !!!

It looks like it’s working fine. Currently, I am in the “night” schedule and the “Inside” settings are used properly. I’ll do more tests tomorrow to confirm that my day settings (“outside”) are used.

Just a (probably) small thing, when configuring the light I selected a color (or light temperature) by mistake while my actual zigbee lamp does not support that (only brightness level).

I got the error message “None of the selected lights can show a color, so a configured color would be ignored. Clear the color fields, or include a light that supports color.” (or the equivalent for white color temperature), but I did not find a way to remove the color/temperature setting, so I was not able to fix this to save the new light and I had to cancel and create it again.

Did I miss something? How can I clear the color/temperature from the configuration panel?

K.

Ha! To be honest I hadn’t thought of that. I assumed Home Assistant handled letting you clear a colour option but it looks like it doesn’t.

I guess I’ll have to think of some nice way to let the user clear some settings without having to cancel and recreate. Good feedback, merci!

1 Like

Hi

I replaced all my remaining entity controller devices with MoLight, and it’s working perfectly!

The control we have in MoLight for the auto-off behavior with the dedicated switch is a very good idea, and it’s perfect for my scenarios.

I have not yet used any of the other “advanced” features, I’ll try to find new ideas :smiley:

K.