Zero-Bounce Bidirectional Light Sync Blueprint (State, Brightness & Color Temp)
Tired of sliders bouncing, jumping, or oscillating back and forth when trying to sync two lights bidirectionally in Home Assistant? This blueprint is the ultimate, bulletproof solution for syncing any two lights (e.g., a virtual screen controller like an ESP32 Cheap Yellow Display, and a physical smart bulb) with zero lag and absolutely zero slider bouncing!
The Problems This Blueprint Solves
1. The Circular "Slider Bounce" Loop
When you sync two dimmable lights bidirectionally, dragging the slider on Light A updates Light B. However, most smart bulbs transition slowly over 1–2 seconds. During this transition, Light B reports intermediate states (e.g., 10%, 30%, 60%) back to Home Assistant. Without protection, your automation thinks: "Aha! The user is adjusting Light B manually!" and pulls Light A's slider back down, causing it to violently jump back and forth.
- The Solution: This blueprint runs in
mode: singlewith a precisedelay: "00:00:01"at the end of each sync. The automation thread remains locked during the transition, quietly ignoring all intermediate feedback loop updates! Once the transition finishes, the lock automatically releases.
2. The none Value API Crash
When a light turns off, Home Assistant deletes its brightness and color_temp_kelvin attributes. Standard sync automations try to pass these none values to the light.turn_on service, causing the Home Assistant API to crash and freeze the sync.
- The Solution: We use an elegant Jinja2
namespacedictionary builder that dynamically compiles only the attributes that actually exist at that exact moment. If an attribute isnone, it is safely omitted from the service call, ensuring 100% crash-free operation!
The Blueprint Code
Save this file as zero_bounce_light_sync.yaml in your Home Assistant /config/blueprints/automation/ directory, or import it directly!
blueprint:
name: Bidirectional Light Sync with Zero-Bounce
description: >
Syncs state, brightness, and color temperature bidirectionally between Light A and Light B
with a 1-second transition lockout to prevent feedback loops and slider bouncing.
domain: automation
input:
light_a:
name: Light A
description: The first light (e.g. your CYD virtual light).
selector:
entity:
domain: light
light_b:
name: Light B
description: The second light (e.g. your physical bedroom lamp).
selector:
entity:
domain: light
mode: single
max_exceeded: silent
variables:
light_a: !input light_a
light_b: !input light_b
trigger:
- platform: state
entity_id: !input light_a
id: a_changed
- platform: state
entity_id: !input light_b
id: b_changed
condition:
- condition: template
value_template: >-
{{ trigger.from_state is not none and trigger.to_state is not none and
trigger.from_state.state != trigger.to_state.state or
trigger.from_state.attributes != trigger.to_state.attributes }}
action:
- choose:
- conditions:
- condition: trigger
id: a_changed
sequence:
- if:
- condition: state
entity_id: !input light_a
state: "on"
then:
- target:
entity_id: !input light_b
data: >-
{% set b = state_attr(light_a, 'brightness') %}
{% set ct = state_attr(light_a, 'color_temp_kelvin') %}
{% set result = namespace(data={}) %}
{% if b is not none %}{% set result.data = dict(result.data, brightness=b) %}{% endif %}
{% if ct is not none %}{% set result.data = dict(result.data, color_temp_kelvin=ct) %}{% endif %}
{{ result.data }}
action: light.turn_on
else:
- target:
entity_id: !input light_b
action: light.turn_off
# Lock execution for 1 second to discard transition feedback loops
- delay: "00:00:01"
- conditions:
- condition: trigger
id: b_changed
sequence:
- if:
- condition: state
entity_id: !input light_b
state: "on"
then:
- target:
entity_id: !input light_a
data: >-
{% set b = state_attr(light_b, 'brightness') %}
{% set ct = state_attr(light_b, 'color_temp_kelvin') %}
{% set result = namespace(data={}) %}
{% if b is not none %}{% set result.data = dict(result.data, brightness=b) %}{% endif %}
{% if ct is not none %}{% set result.data = dict(result.data, color_temp_kelvin=ct) %}{% endif %}
{{ result.data }}
action: light.turn_on
else:
- target:
entity_id: !input light_a
action: light.turn_off
# Lock execution for 1 second to discard transition feedback loops
- delay: "00:00:01"
Pro-Tip: Inherit Friendly Names Automatically (For ESPHome/Virtual controllers)
If you are using a virtual screen or controller (like an ESPHome Cheap Yellow Display) as Light A, you can make it automatically inherit the friendly name of your physical bulb (Light B)!
Simply point the ESPHome homeassistant text sensor directly to the physical bulb (light.bedroom1) instead of the virtual entity:
# Inside your ESPHome YAML
text_sensor:
- platform: homeassistant
id: ha_name_l1
entity_id: light.bedroom1 # <-- Point this directly to your physical Light B!
attribute: friendly_name
on_value:
then:
- lvgl.label.update:
id: l1_name_label
text: !lambda |-
if (x.empty()) return id(cyd_l1)->get_name();
return x;
Now, whenever you rename your physical bulb in Home Assistant, the virtual display will automatically rename its screen buttons to match!
Credits & Feedback
If you find this blueprint helpful, please leave a comment or drop a
! Let me know if you have any questions or optimizations.