Accent & Signal Light is a Home Assistant custom integration that lets you manage accent and notification lights without writing complex bookkeeping automations. Instead of tracking "which automation last changed the colour of my lamp" across dozens of rules, Accent & Signal Light gives every use-case its own named slot in a priority-sorted stack or queue. When a slot is cleared the light automatically reverts to the next lower layer — zero cleanup automation required.
- Concepts — the three-layer model
- Installation
- Configuration
- Entities created
- Services reference
- Example automations
- Architecture notes
Accent & Signal Light wraps a single physical light entity (or light group) in a three-layer priority model. Layers are evaluated from highest to lowest; the topmost non-empty layer controls the physical light.
┌───────────────────────────────────────────────────────────┐
│ SIGNAL LAYER (highest priority) │
│ Priority queue of named signals. │
│ Highest-priority signal controls the light. │
│ When cleared, next signal takes over (or falls through). │
├───────────────────────────────────────────────────────────┤
│ ACCENT LAYER (medium priority) │
│ Priority stack of named accents. │
│ Highest-priority accent controls the light when no │
│ signal is active. │
│ When cleared, next accent takes over (or falls through). │
├───────────────────────────────────────────────────────────┤
│ BASE LAYER (lowest priority) │
│ Default light state — on/off, colour, brightness. │
│ Exposed as a standard HA light entity. │
│ Active only when accent and signal layers are empty. │
└───────────────────────────────────────────────────────────┘
│
▼ controls
┌──────────────┐
│ Physical │
│ light entity │
└──────────────┘
The base layer is the "ambient" default. Set it once (e.g. warm white at 40 %
brightness) and it will be applied whenever nothing else is active. It is
exposed as a standard light entity, so you can control it from dashboards,
voice assistants, or automations exactly like any other light.
Accents are long-lived overrides — for example:
| Accent name | Priority | Use-case |
|---|---|---|
holiday_4th |
20 | Red/white/blue all week |
movie_mode |
60 | Dim warm light when the TV is on |
party |
80 | Colour cycle while the party playlist plays |
Multiple accents can coexist. The one with the highest priority controls
the light. When you clear party, movie_mode (if still present) takes over
immediately — no extra automation needed. If the base light is off, accents are
still tracked but they will not turn the physical light on by themselves.
Accents are added/removed via the signal_light.set_accent and
signal_light.clear_accent services.
Signals are short-lived notifications — for example:
| Signal name | Priority | Use-case |
|---|---|---|
doorbell |
90 | Flash white when someone rings the bell |
laundry_done |
70 | Slow blue pulse when the washing is finished |
timer_expired |
80 | Red flash when a kitchen timer ends |
The highest-priority active signal overrides the accent and base layers. When it is cleared (e.g. the user acknowledges the doorbell) the next signal (if any) takes over, or the accent/base layer resumes. If the base light is off, signals remain queued and only turn the light on when their priority is greater than the configured signal wake priority.
Signals are added/removed via the signal_light.set_signal and
signal_light.clear_signal services.
- Open HACS → Integrations → ⋮ → Custom repositories.
- Add
https://github.com/arkane-systems/ha-accent-signal-lightas an Integration repository. - Search for Accent & Signal Light and click Download.
- Restart Home Assistant.
- Copy the
custom_components/signal_light/directory into your HA configuration directory undercustom_components/. - Restart Home Assistant.
- Go to Settings → Devices & Services → Add Integration.
- Search for Accent & Signal Light and click it.
- Fill in the form:
- Name — a label for this instance (e.g. "Living Room Accent & Signal Light").
- Underlying light entity — the
light.*entity that Accent & Signal Light will control (may be a light group). - Signal wake priority — signals must have a priority greater than this value to turn the light on while the base layer is off.
- Click Submit.
You can create multiple Accent & Signal Light instances — one per physical light or group you want to manage this way.
Each Accent & Signal Light instance creates five entities, all grouped under a single HA device named after the instance.
| Entity type | Name suffix | Description |
|---|---|---|
light |
Base | Base-layer light — controls the default state. |
sensor |
Active Signal | Name of the top-priority signal, or "none". |
sensor |
Active Accent | Name of the top-priority accent, or "none". |
sensor |
Signal Queue | Count of queued signals; full queue in attrs. |
sensor |
Accent Stack | Count of stacked accents; full stack in attrs. |
The base light (light.<name>_base) behaves like any standard HA light. Use
it in dashboards or automations to set the default ambient state. Note that
its reported state always reflects the base layer, even when an accent or
signal is overriding the physical light.
All sensor entities report their data as both state and extra_state_attributes.
Active Signal / Active Accent sensors:
state: "doorbell"
attributes:
priority: 90
hs_color: [0, 0]
brightness: 255
Signal Queue / Accent Stack sensors:
state: 2
attributes:
queue:
- name: "doorbell"
priority: 90
attrs: {hs_color: [0, 0], brightness: 255}
- name: "laundry_done"
priority: 70
attrs: {hs_color: [240, 80], brightness: 128}
All services are in the signal_light domain and target the base-layer
light entity (or its device) of the instance you want to control.
Add or update a signal in the priority queue. If a signal with the given name already exists it is replaced (handy for updating colour without a separate clear).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_id |
entity | ✓ | — | The base-layer light of the target instance |
signal_name |
string | ✓ | — | Unique name for this signal |
priority |
integer | — | 50 | Higher values take precedence |
brightness |
integer | — | — | 0–255 |
brightness_pct |
float | — | — | 0–100 % |
color_name |
string | — | — | Named colour (e.g. "red") |
hs_color |
list | — | — | [hue, saturation] |
rgb_color |
list | — | — | [r, g, b] |
color_temp_kelvin |
int | — | — | Kelvin (1000–10000) |
effect |
string | — | — | Effect name |
transition |
float | — | — | Seconds |
Remove a signal from the priority queue.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
entity | ✓ | The base-layer light |
signal_name |
string | ✓ | Name of the signal to remove |
Add or update an accent in the priority stack.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_id |
entity | ✓ | — | The base-layer light of the target instance |
accent_name |
string | ✓ | — | Unique name for this accent |
priority |
integer | — | 50 | Higher values take precedence |
brightness |
integer | — | — | 0–255 |
brightness_pct |
float | — | — | 0–100 % |
color_name |
string | — | — | Named colour (e.g. "red") |
hs_color |
list | — | — | [hue, saturation] |
rgb_color |
list | — | — | [r, g, b] |
color_temp_kelvin |
int | — | — | Kelvin (1000–10000) |
effect |
string | — | — | Effect name |
transition |
float | — | — | Seconds |
Remove an accent from the priority stack.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
entity | ✓ | The base-layer light |
accent_name |
string | ✓ | Name of the accent to remove |
# Turn the living-room signal light white when the doorbell is pressed.
# The flash lasts until the automation clears it (e.g. after 30 s).
automation:
- alias: "Doorbell: signal on"
trigger:
- platform: state
entity_id: binary_sensor.doorbell
to: "on"
action:
- service: signal_light.set_signal
target:
entity_id: light.living_room_signal_light_base
data:
signal_name: doorbell
priority: 90
hs_color: [0, 0] # white
brightness: 255
- alias: "Doorbell: signal off after 30 s"
trigger:
- platform: state
entity_id: binary_sensor.doorbell
to: "on"
for: "00:00:30"
action:
- service: signal_light.clear_signal
target:
entity_id: light.living_room_signal_light_base
data:
signal_name: doorbell# Apply a patriotic accent at midnight starting July 1st (i.e. the very
# beginning of July 1st) and remove it at midnight starting July 5th
# (i.e. the very beginning of July 5th, so the accent is active through
# the end of July 4th). Any higher-priority accents or signals still take
# precedence.
automation:
- alias: "4th of July accent: activate"
trigger:
- platform: time
at: "00:00:00"
condition:
- condition: template
value_template: "{{ now().month == 7 and now().day == 1 }}"
action:
- service: signal_light.set_accent
target:
entity_id: light.living_room_signal_light_base
data:
accent_name: fourth_of_july
priority: 20
rgb_color: [255, 0, 0] # start red; rotate via separate automation
- alias: "4th of July accent: deactivate"
trigger:
- platform: time
at: "00:00:00"
condition:
- condition: template
value_template: "{{ now().month == 7 and now().day == 5 }}"
action:
- service: signal_light.clear_accent
target:
entity_id: light.living_room_signal_light_base
data:
accent_name: fourth_of_july# Activate movie-mode (dim, warm) while the TV is on. Because this has a
# higher priority than the holiday accent, it overrides it during movie time.
automation:
- alias: "Movie mode: activate"
trigger:
- platform: state
entity_id: media_player.living_room_tv
to: "playing"
action:
- service: signal_light.set_accent
target:
entity_id: light.living_room_signal_light_base
data:
accent_name: movie_mode
priority: 60
brightness: 40
color_temp_kelvin: 2700
- alias: "Movie mode: deactivate"
trigger:
- platform: state
entity_id: media_player.living_room_tv
to: "idle"
- platform: state
entity_id: media_player.living_room_tv
to: "off"
action:
- service: signal_light.clear_accent
target:
entity_id: light.living_room_signal_light_base
data:
accent_name: movie_modeAdd the four sensor entities to a dashboard card to see what is currently active at a glance:
type: entities
title: Living Room Accent & Signal Light
entities:
- entity: light.living_room_signal_light_base
- entity: sensor.living_room_signal_light_active_signal
- entity: sensor.living_room_signal_light_active_accent
- entity: sensor.living_room_signal_light_signal_queue
- entity: sensor.living_room_signal_light_accent_stackThe integration uses a custom SignalLightCoordinator (not HA's built-in
DataUpdateCoordinator, which is designed for polling). The coordinator
owns all mutable state and is the single place where state transitions happen.
Entities register a _remove_listener callback in async_added_to_hass and
de-register in async_will_remove_from_hass. The coordinator calls all
listeners after every mutation, triggering a write_ha_state on each entity.
Both the accent stack and signal queue are stored as Python lists sorted by
priority descending. Insertion and deletion are O(n) — entirely adequate
for the expected number of entries (typically < 10). The name field acts as
a primary key: setting an existing name replaces the entry.
The base-layer light entity extends RestoreEntity. On startup it reads its
previous state from the HA recorder and re-applies it to the coordinator,
ensuring the base layer survives restarts.
Accent and signal layers are intentionally not persisted. They are
expected to be re-populated by automations (e.g. via homeassistant.start
triggers). This avoids stale signals lingering after a restart.
Every time any layer changes, _async_apply_state() is called. It evaluates
the effective state top-down and calls light.turn_on or light.turn_off on
the underlying entity. The call is made with blocking=True to ensure the
light state is consistent before listeners are notified. When the base layer is
off, accents never wake the light and only signals above the configured wake
priority may do so; queued accents/signals still take effect immediately once
the base layer is turned back on.
On startup, if the underlying light is not yet available, apply calls are deferred. The coordinator listens for the underlying entity becoming available and re-applies the effective state once it comes online.
Before any turn_on call, color descriptors are normalized so only one of
color_name, hs_color, rgb_color, rgbw_color, rgbww_color, xy_color,
or color_temp_kelvin is sent in the payload.
The base-layer light mirrors capability metadata from the underlying light so UI controls stay aligned with the real hardware/group:
supported_color_modessupported_featureseffect_listmin_color_temp_kelvin/max_color_temp_kelvin
The four signal_light.* services are registered as entity services on the
light platform. This means:
- HA validates the
entity_id/device_idtarget automatically. - The service is routed to
async_handle_set_signal/ etc. on the matchingSignalBaseLightentity. - Multiple instances are fully isolated — calling the service with one
instance's
entity_idnever affects another.
