Intelligent, solar-aware lighting automation for your home.
Let the sun set the schedule — not your watch.
🌅 10 astral zones per area — dawn to dusk, golden hour to night.
🔄 Multiple channels per area — separate light groups, independent schedules.
🧠 Input-driven activities — motion, binary sensors, timers, buttons, events.
🎯 Scene-based execution — plain scenes, Hue scenes, scripts.
⚡ Dynamic rules & priority overrides — N-active layers & temporary slot exclusions.
🔍 Live deviation tracking — real-time monitoring of manual overrides & scene states.
🌡️ Heatmaps & presence simulation — realistic occupancy replay based on usage history.
📊 Built-in analytics — uPlot charts with real Recorder data.
🕸️ Full Web UI — no YAML config needed after setup.
Note
Beta / Active Development — functional, tested, and proven in daily live use. The configuration model may still evolve; expect occasional breaking changes between versions. Feedback welcome via GitHub Issues.
- Why this exists
- Prerequisites
- Quick Start
- Installation
- Core Concept
- Inputs & Activities
- Frontend Tabs
- Services
- Diagnostics
- Troubleshooting
- Known Limitations
- Contributing
- License
Home Assistant can turn lights on and off, but matching light scenes to the sun's position — astronomical dawn, civil dusk, golden hour — still requires complex automations, templates, and manual scheduling.
Twilight replaces all of that with a single integration. You define areas, each with solar-aligned time slots and optional motion/input overrides, and Twilight handles the transitions. No template sensors. No scene-to-time matching. No spaghetti automations.
- Home Assistant 2025.5 or newer
- Install — copy the integration (see Installation)
- Restart Home Assistant
- Configure — add
twilight:toconfiguration.yamland restart again - Open the Twilight panel in the sidebar
- Create an Area — select an HA area, assign lights to channels
- Assign Scenes — map your scenes to the astral time slots
- Add Inputs — attach motion sensors, buttons, or timers to trigger activities
-
Copy the
custom_components/twilightdirectory to your HAconfig/custom_components/folder:cp -r custom_components/twilight /path/to/homeassistant/config/custom_components/
-
Restart Home Assistant.
-
Add the following to your
configuration.yaml:twilight: -
Restart Home Assistant again.
-
Open the sidebar and click Twilight to launch the panel.
Warning
The twilight: line in configuration.yaml is required — it enables the
integration. All configuration then happens in the sidebar panel.
- Open the Twilight panel in the sidebar
- Click "Add Area" and select one of your Home Assistant areas
- Twilight creates 10 astral time slots automatically, aligned to your location's sun position, plus a default set of inputs/activities from the area template
- In the Channels section, assign your light entities to the Primary channel
- For each time slot, select a scene or script that should play during that period
- Click Save — the engine starts immediately
Every enabled Area runs the same pipeline: sensors feed inputs, inputs request activities, and exactly one activity at a time controls the lights on each channel.
Sensors → Inputs → Coordinator (one active activity) → Action → Channels/Slots → Lights
No activity active → lights are off. A time slot only decides which scene would play — the schedule alone never turns anything on. Something has to request an activity first (motion, a button, a timer, …). This is the most common point of confusion when setting up a new area: if nothing reacts, check whether an input is actually configured to trigger the Motion or Manual activity, not whether the slot has a scene assigned.
Each Area maps to a Home Assistant area. You configure its light groups (channels), sensors, inputs, and activities independently. An engine runs per enabled area, so different rooms can have completely different logic.
Every area has a Primary channel with 10 fixed astral slots:
Night → Astronomical dawn → Nautical dawn → Civil dawn → Golden dawn → Day → Golden dusk → Civil dusk → Nautical dusk → Astronomical dusk
Their order and solar zone are fixed — you can only edit each slot's assigned scene/script and an offset in minutes.
You can add up to 3 custom channels per area, each with a free-form slot
list, to control a separate light group on its own schedule while sharing the
same sensors and inputs (e.g. outdoor lights that don't follow the same curve
as your indoor lighting). Each custom slot starts either at a sun event
(any astral zone, with an offset) or a fixed clock time (HH:MM), and has
its own scene/script assignment.
| Channel Type | Slots | Use Case |
|---|---|---|
| Primary | 10 fixed astral zones | Main indoor lighting — follows the sun |
| Custom (up to 3) | Free-form — sun event or fixed time + offset | Outdoor lights, accent lighting, separate schedules |
Each channel also has its own slot transition duration (default 60s) — the crossfade time when moving from one slot to the next.
Twilight supports different scene sources, assigned per slot:
- Plain Scenes — standard Home Assistant scenes created via the HA UI or
configuration.yaml - Hue Scenes — Hue scenes from your Hue Bridge (requires Hue Integration). Scenes with multi-color or temperature palettes can optionally enable dynamic playback with configurable rotation speed.
- Scripts — any Home Assistant script that can control lights
Sensors are passive readings without a guaranteed event on every change — they support inputs rather than triggering activities themselves. Today, Twilight supports one sensor type:
- Illuminance (Lux) — one per area. Add one or more
sensor.*entities; if you add more than one, their values are combined with an aggregation method: Minimum (default), Maximum, Average, or Median. The aggregated value is what motion inputs compare against their illuminance threshold.
Inputs are the event sources that request an activity. Every input has a name and can be configured in the UI.
| Input Type | Triggers on | Notes |
|---|---|---|
| Motion | Any of its binary_sensor.*/device_tracker.* entities turning on/home |
No release when the sensor goes quiet — a motion sensor drops within seconds, so the activity ends on its timer instead. Optional illuminance threshold; re-trigger mode (every flip / first flip); optional lux watch window to retry after being blocked by light |
| Binary | Any/all/count of its binary_sensor.*/input_boolean.* entities (configurable aggregation) |
Sends a release when turned off |
| Event | A event.* entity's state change, optionally filtered to an event type |
Fires once per matching event, no release |
| Timer | A daily fixed clock time (HH:MM:SS) |
No entity needed — useful for a nightly reset |
| Button | Manually via the Areas panel or the twilight.trigger_input service |
Click mode fires once; Toggle mode sends a release on the second call — see Manual Triggers |
Each input targets either an Activity or a Rule (via "Target Type"). Binary and toggle-mode button inputs can target either; motion, event, timer, and click-mode button inputs always target an Activity.
An activity owns the lights while it is active — the coordinator only ever lets one activity run at a time; a new request immediately displaces whatever was active before.
| Option | Description |
|---|---|
| Suppressed Inputs | List of inputs to ignore while this activity is active (e.g. a sleep activity suppressing the motion input) |
| Enable Auto-Expire | Whether the activity runs a timer at all. Unchecked, it ends only when a binary input turns off, a toggle button is switched off, or another activity takes over. |
| Auto-Expire Duration | How long the timer runs, once enabled. Empty uses the default of 3 hours. |
What actually happens to the lights on start (action) and on end (release action):
| Action type | Effect | Applies to |
|---|---|---|
| Schedule | Plays the current channel/slot's scene; allows selecting active channels and slots | Action |
| Entity/Script | Activates one chosen scene or script directly, ignoring the schedule | Action |
| Light Control | Sets specific light entities directly — on/off, brightness, color temperature, or RGB | Action |
| Block | Does nothing to the lights ("Block Only / No Action") | Action |
| Turn Off | Turns off all of this activity's slot entities | Release |
| Reset Command | Sends a fixed command (on/off/toggle/activate) to input_boolean, switch, or script entities you choose — for driving something other than lights |
Action (no release phase) |
| Presence Simulation | Simulates presence by playing back an activity's light behavior timed by historical or profile heatmap probabilities | Action |
New activities in the editor start pre-filled with Schedule/Turn Off as a convenient starting point, but this is not a backend fallback: if you explicitly clear an activity's action or release action, it does nothing on that phase — there is no implicit default.
For Schedule actions you can choose per channel and slot in the UI which sections are active. If a slot is unchecked for a channel, that channel stays off during that period.
Activities using presence simulation make an empty home look occupied by re-creating realistic lighting patterns. You select an existing activity (such as your standard motion activity) to copy its light behavior and duration, and Twilight triggers it based on motion density probabilities throughout the day:
- Data source: evaluate live recorder history (a rolling 30-day window) or pick one of up to 5 saved heatmap profiles per area (saved from the Heatmap card, where you choose the range
1w–6mand resolution). - Lux gating: choose when simulated motion may turn lights on (Dusk/Dawn [default], illuminance threshold, or Always).
- Eligible activities: any activity that controls lights (Schedule, Entity, or Light).
- Frequency: scale how often simulated triggers fire, from very low to very high, relative to the measured motion density.
Trigger probabilities are based on how many days actually saw activity at a given time, so a room used every evening is simulated more often than one used once. A running simulation resumes automatically after a Home Assistant restart.
Four activities are preconfigured on every new area (from the default area template):
- Motion — triggered by the motion input, runs for 5 minutes by default (auto-prolonged while motion is still detected)
- Manual — triggered by the manual trigger input, suppresses motion while active, no timer
- Sleep — triggered by the binary sleep input, suppresses motion while active, no timer (ends when the switch goes off)
- Reset — triggered by the reset button, sends a reset command (no light control, never expires)
Edit or replace them via the area template, and add your own for anything else — a guest mode, extra motion inputs with different lux thresholds, and so on.
Auto-Expire determines how long an activity remains active after being triggered:
- Explicit Timer Switch: Use Enable Auto-Expire to turn the timer on or off explicitly. Unchecked, an activity runs indefinitely until explicitly released (e.g. a binary input turning off, a toggle button switching off, or another activity taking over). A manual override or sleep activity typically has Auto-Expire disabled.
- Automatic Prolongation: When the auto-expire timer runs out, Twilight automatically checks whether any of its triggering inputs are still active (e.g., a motion sensor holding
on, a binary contact, or an active toggle button). If active, the timer restarts for the full duration instead of ending the activity. The configured duration is a minimum lifetime, not a hard deadline — as long as you remain active in the room, the activity stays on. - Lux-Aware Prolongation: For motion inputs, prolongation re-evaluates daylight levels using the exact same illuminance threshold as initial activation. If natural daylight has filled the room above the threshold, the timer will not extend and the light turns off naturally.
Button inputs need no HA entity — they're triggered directly from the Areas
panel, or from an automation via the twilight.trigger_input service (see
Services). Two modes:
- Click — fires once, fire-and-forget (e.g. a dashboard "Reset" button)
- Toggle — tracks on/off state, sent to the frontend live
Optionally, enable Create Entity on a button input to also expose it as a
real Home Assistant entity: click mode creates a button.*, toggle mode
creates a switch.*. Useful if you want to trigger it from a physical
dashboard tile, voice assistant, or another automation without going through
the service call.
Rules are a temporary, parallel override layer: while a trigger input (a switch or toggle button) is active, active Rules modify the behavior of the currently running activity or configuration — they do not control lights on their own. Unlike Activities (only one active at a time), any number of Rules can be active in parallel.
Today there is one Rule type:
| Rule Type | Overrides | Trigger & Condition |
|---|---|---|
| Activity Rule | Channel/slot selection, auto-expire behavior, and input suppression of a target Activity | Triggered by binary or toggle button input; active only when target Activity is running |
An Activity Rule affects exactly one Activity. While active, its settings override the Activity's own settings: what is checked in the Rule applies. If multiple Rules target the same Activity, "exclude wins" — a slot is active for a channel only if it is checked in all active Rules.
When configuring an Activity Rule in the editor, options for Auto-Expire behavior, channel slots, or input suppression are dynamically displayed only if applicable to the selected target activity.
An Activity Rule can also optionally control what happens to the target Activity's auto-expire countdown while the Rule is active ("Auto-Expire Behavior"):
| Mode | Effect |
|---|---|
| None (default) | No effect — the timer runs unaffected. |
| Pause | Freezes the timer at its current remaining time; resumes with that same remaining time once the Rule ends. |
| Suspend | Freezes the timer at zero; the Activity expires immediately once the Rule ends (unless another condition, like active motion, still holds it open). |
| Restart | Freezes the timer at the full configured duration; the Activity gets a fresh full timer once the Rule ends. |
If the target Activity has auto-expire disabled, there is no timer to freeze and all four modes do nothing.
Rule-Granted Auto-Expire: for a target Activity that has no timer of its own (Auto-Expire disabled), a Rule can instead lend it one — enable Auto-Expire and set a duration directly on the Rule. The timer starts once the Rule activates and keeps running on its own after the Rule ends, so the Activity still turns off cleanly instead of running forever. Re-triggering the Activity while the Rule is still active resets the loan to its full duration; a re-trigger after the Rule has ended no longer has a duration to draw from and runs unbounded, like any other Activity without Auto-Expire. This is mutually exclusive with the Auto-Expire Behavior modes above — a Rule only ever uses one or the other, depending on whether its target Activity already has a timer.
If a Rule's target Activity is edited afterwards and no longer supports one of the Rule's configured options (e.g. Auto-Expire Behavior on an Activity that gained its own timer), the Rule is automatically disabled and flagged with a warning in the Rules list until reconfigured.
A Rule can also suppress other inputs while it is active — e.g. an "Ambient" Rule that ignores the motion sensor input for its duration. While a matching Rule is active, its suppressed inputs replace the target Activity's own input suppression. Suppressed inputs stop reacting to triggers until the Rule ends; this uses the same disabler mechanism as an Activity's own input suppression, just keyed to the Rule instead. A Rule cannot suppress the input that triggers it.
Configure Rules via the Rules card in an area's detail view or in the Area Template.
Twilight ships with a full Lit-based sidebar panel — no YAML editing needed after initial setup.
Overview cards (with a sparkline day chart and live stats per area) plus a detail view per area for all configuration described above: channels, sensors, inputs, activities, an activity heatmap card (visualizing weekday sensor patterns from live recorder data or up to 5 saved heatmap profiles), and a live status card showing the currently active activity and what triggered it. New areas stay a local, unsaved draft until you click Save.
- Day view — the sun's path through today's astral zones, with live time indicator
- Year view — the same zones plotted across the whole year, so you can see how slot durations shift with the seasons
- Area selector — preview area-specific primary-channel slot offsets on both charts
- Includes a demo mode with example locations, independent of any configured area
uPlot charts backed by real Home Assistant Recorder data — dual Y-axes, a time range selector, and per-area motion shown as background bands so you can correlate light changes with actual sensor activity.
The Area template setting is the blueprint used to seed every new area: its default inputs (motion, a manual trigger, a reset trigger, a binary sleep trigger, a reset button, and an ambient rule trigger), default activities (Motion, Manual, Sleep, Reset), a default Ambient rule, an Ambient example custom channel, and the default Primary-channel slots (per-zone offsets and scenes). Edit it once in Settings and every area you create afterwards starts pre-wired instead of empty — existing areas are unaffected. A granular Reset to System Defaults dialog lets you restore individual inputs/activities/rules/channels without resetting the whole template.
The Backup & Restore button in the Settings footer downloads a full snapshot of the store — global settings, area template, and every area's configuration (no runtime state) — as a single JSON file, and can restore from one.
Restoring completely replaces the current configuration: areas not present in the backup file are deleted, and all running engines are torn down and rebuilt from the imported config immediately — no Home Assistant restart required. A confirmation dialog is shown before any destructive restore. Lights already on keep their last state until the next regular slot/activity change; nothing is force-switched during the restore itself.
Inside the Backup & Restore dialog, a Factory Reset action is also available to reset Twilight back to its initial system defaults — deleting all custom areas and resetting global settings and templates after explicit confirmation.
Fires or releases a button-type input from an automation or script, without
needing a real HA entity for it.
| Field | Required | Description |
|---|---|---|
area_id |
Yes | The Home Assistant area containing the input |
input_id |
Yes | The button input's id or name (visible in the inputs list) |
active |
No | For toggle-mode buttons: true/false to set the state explicitly |
service: twilight.trigger_input
data:
area_id: living_room
input_id: reset_btnDownload a full diagnostics snapshot via Settings → Devices & Services → Twilight → ⋮ → Download diagnostics in Home Assistant. It contains the current store (all areas + global settings) and, per area, the live run state, the resolved scene map (which entity plays in which slot), and the last known state of every controlled light — useful when reporting a bug.
Note
The file contains your real entity IDs, scene names, and area names — nothing secret (no tokens or credentials), but potentially revealing about your home. Review it before attaching it to a public GitHub issue.
| Problem | Solution |
|---|---|
| Panel shows "Engine not running" | Ensure twilight: is in configuration.yaml and HA was restarted |
| Lights never turn on | No activity is active — check that an input is actually wired to an activity; a slot having a scene assigned is not enough, see Core Concept |
| Scene shows no lights | Check if the scene still exists and is reachable; if it involves a Hue Smart Scene, ensure the Hue Integration is loaded |
| Motion sensors not responding | Verify the lux threshold in the motion input — it may be suppressed by high ambient light; check the aggregated lux value on the area's status card |
| Lights flash on restart | This shouldn't happen — Twilight uses boot-catch-up to avoid unwanted activation. Please open an issue |
| Area not appearing in panel | Ensure the HA area exists and it was saved — new areas stay a draft until you click Save |
Go to Settings → Devices & Services → Integrations → Twilight, open the three-dot menu, and select Enable debug logging. No restart required — this only lasts until the next restart.
When reporting a bug, ideally share the full log. If you'd rather not share unrelated data, filter it down to just Twilight's entries:
grep 'twilight' home-assistant.log > twilight.logThe diagnostics download is usually more useful for configuration/state questions — reach for debug logging when you need to see the actual event-by-event decisions (why an activity didn't trigger, why a retry fired, etc.).
- Only one illuminance sensor per area (multiple entities can feed into it)
- No multi-language support yet (English only)
Found a bug or have an idea? Open an issue on GitHub.
MIT License


