LEDDs v2

Overview
LEDDs is an open-hardware, DIY, trailing-edge dimmer designed specifically for dimmable LED bulbs. It is intended for DIY users who have experience with mains-powered electronics.
Hardware
The project is fully open source, including:
- schematics and PCB files
- 3D printable enclosure

- MCU module: ESP32-C3-12F
- Mains voltage: 230 V AC, 50Hz
- Dimming method: trailing-edge phase cut
- Load type: dimmable LED bulbs (capacitive)
⚠️ Warning: This device operates at mains voltage. Building and using it requires experience with high-voltage electronics. Improper assembly can result in serious injury, fire, or equipment damage.
ESPHome configuration
LEDDs is configured using ESPHome and exposes itself as a dimmable light entity. Complete ESPHome YAML configurations are provided in the project repository: https://github.com/VasilKalchev/LEDDs/tree/main/fw/esphome/yaml.
Example, trimmed configuration for Home Assistant
substitutions:
device_friendly_name: "LEDDs 2"
# user configuration
dim_step: 6.25 # 10 % | 6.25 %
gamma_correct: 2.2
minimum_level: 15 %
restore_mode: ALWAYS_OFF # ALWAYS_OFF | ALWAYS_ON
transition_length: 300 ms
feedback_blink_length: 40 ms
# IDs
light_id: dimmer_light
ac_dimmer_id: ac_dimmer_output
rotary_encoder_id: rotary_encoder_sensor
rotary_encoder_btn_id: rotary_encoder_btn_sensor
feedback_led_id: red_led
# pinout
led_2_pin: GPIO2
zc_pin: GPIO3
re_b_pin: GPIO4
re_a_pin: GPIO5
led_1_pin: GPIO6
gate_pin: GPIO7
led_3_pin: GPIO8
re_btn_pin: GPIO9
relay_pin: GPIO10
esphome:
name: "ledds2"
friendly_name: ${device_friendly_name}
comment: "trailing edge dimmer"
project:
name: "VasilKalchev.LEDDs"
version: "2.0"
esp32:
variant: esp32c3
framework:
type: esp-idf
version: recommended
logger:
level: WARN
wifi:
ap:
captive_portal:
improv_serial:
next_url: https://github.com/VasilKalchev/LEDDs/tree/v2.0
ota:
platform: esphome
on_begin:
then:
- output.turn_off:
id: ${ac_dimmer_id}
api:
# =====
light:
- platform: monochromatic
id: ${light_id}
name: "Bulb"
output: ${ac_dimmer_id}
icon: "mdi:lightbulb-variant"
default_transition_length: ${transition_length}
gamma_correct: ${gamma_correct}
restore_mode: ${restore_mode}
effects:
- pulse:
name: "Pulse (low, fast)"
transition_length: 4s
update_interval: 4s
min_brightness: 1%
max_brightness: 50%
- pulse:
name: "Pulse (low, slow)"
transition_length: 7s
update_interval: 7s
min_brightness: 1%
max_brightness: 50%
- pulse:
name: "Pulse (high, fast)"
transition_length: 4s
update_interval: 4s
min_brightness: 50%
max_brightness: 100%
- pulse:
name: "Pulse (high, slow)"
transition_length: 7s
update_interval: 7s
min_brightness: 50%
max_brightness: 100%
- pulse:
name: "Pulse (full, fast)"
transition_length: 3s
update_interval: 3s
min_brightness: 1%
max_brightness: 100%
- pulse:
name: "Pulse (full, slow)"
transition_length: 10s
update_interval: 10s
min_brightness: 1%
max_brightness: 100%
- flicker:
name: "Flicker (soft)"
alpha: 85%
intensity: 3%
- flicker:
name: "Flicker (intense)"
alpha: 85%
intensity: 4%
- platform: monochromatic
id: red_led
name: "LED: red"
output: led1
icon: "mdi:led-outline"
entity_category: "config"
default_transition_length: 1000 ms
restore_mode: RESTORE_DEFAULT_ON
initial_state:
state: on
brightness: 100 %
- platform: monochromatic
id: blue_led
name: "LED: blue"
output: led2
icon: "mdi:led-outline"
entity_category: "config"
default_transition_length: 1000 ms
restore_mode: RESTORE_DEFAULT_ON
initial_state:
state: on
brightness: 100 %
- platform: monochromatic
id: white_led
name: "LED: white"
output: led3
icon: "mdi:led-outline"
entity_category: "config"
default_transition_length: 1000 ms
restore_mode: RESTORE_DEFAULT_ON
initial_state:
state: on
brightness: 100 %
- platform: status_led
id: blue_status_led
pin:
number: ${led_1_pin}
allow_other_uses: true
sensor:
- platform: rotary_encoder
id: ${rotary_encoder_id}
name: "Knob"
icon: "mdi:knob"
internal: true
pin_a: ${re_a_pin}
pin_b: ${re_b_pin}
min_value: -1
max_value: 1
publish_initial_value: true
restore_mode: ALWAYS_ZERO
filters:
- debounce: 20 ms
on_clockwise:
then:
- if:
condition:
- binary_sensor.is_off: ${rotary_encoder_btn_id}
then:
- script.execute:
id: feedback_blink
condition: !lambda 'return id(${light_id}).remote_values.get_brightness() < 1.0;'
- light.dim_relative:
id: ${light_id}
relative_brightness: ${dim_step} %
transition_length: 0 ms
else:
- script.execute:
id: feedback_blink
condition: !lambda 'return id(${light_id}).get_current_effect_index() < (uint32_t)id(${light_id}).get_effect_count();'
- script.execute:
id: walk_effects
forward: true
on_anticlockwise:
then:
- if:
condition:
- binary_sensor.is_off: ${rotary_encoder_btn_id}
then:
- script.execute:
id: feedback_blink
condition: !lambda 'return id(${light_id}).remote_values.get_state() > 0.0;'
- light.dim_relative:
id: ${light_id}
relative_brightness: !lambda 'return (${dim_step} * -1) / 100.0;'
transition_length: 0 ms
else:
- script.execute:
id: feedback_blink
condition: !lambda 'return id(${light_id}).get_current_effect_index() > 0;'
- script.execute:
id: walk_effects
forward: false
binary_sensor:
- platform: gpio
id: ${rotary_encoder_btn_id}
name: "Knob button"
internal: true
pin:
number: ${re_btn_pin}
inverted: true
ignore_strapping_warning: true
entity_category: diagnostic
use_interrupt: true
interrupt_type: ANY
filters:
- delayed_on_off: 10 ms
on_click:
min_length: 50 ms
max_length: 500 ms
then:
- light.toggle:
id: ${light_id}
button:
- platform: restart
id: restart_button
name: "Restart"
entity_category: config
device_class: restart
text_sensor:
- platform: version
name: "ESPHome version"
hide_timestamp: true
power_supply:
- id: ac_live
pin:
number: ${relay_pin}
inverted: false
enable_time: 100 ms
keep_on_time: 500 ms
enable_on_boot: false
output:
- platform: ac_dimmer
id: ${ac_dimmer_id}
gate_pin:
number: ${gate_pin}
inverted: yes
zero_cross_pin:
number: ${zc_pin}
inverted: no
mode:
input: true
method: trailing
init_with_half_cycle: true
zero_means_zero: true
min_power: ${minimum_level}
max_power: 100 %
power_supply: ac_live
- platform: ledc
id: led1
pin:
number: ${led_1_pin}
allow_other_uses: true
frequency: 200 Hz
- platform: ledc
id: led2
frequency: 200 Hz
pin:
number: ${led_2_pin}
ignore_strapping_warning: true
- platform: ledc
id: led3
frequency: 200 Hz
pin:
number: ${led_3_pin}
ignore_strapping_warning: true
script:
- id: walk_effects
parameters:
forward: bool
then:
- light.turn_on:
id: ${light_id}
effect: !lambda |-
size_t const effects_count = id(${light_id}).get_effect_count();
ESP_LOGV("walk_effects", "effects count: %d\n", effects_count);
uint32_t effect_ndx = id(${light_id}).get_current_effect_index();
ESP_LOGV("walk_effects", "effect ndx: %d (%s)\n", effect_ndx, id(${light_id}).get_effect_name_by_index(effect_ndx).c_str());
if (forward == true) {
if (effect_ndx < effects_count) {
effect_ndx += 1;
}
} else {
if (effect_ndx > 0) {
effect_ndx -= 1;
}
}
ESP_LOGV("walk_effects", "new ndx: %d (%s)\n", effect_ndx, id(${light_id}).get_effect_name_by_index(effect_ndx).c_str());
return id(${light_id}).get_effect_name_by_index(effect_ndx);
- id: feedback_blink
parameters:
condition: bool
then:
- if:
condition:
lambda: 'return condition == true;'
then:
- light.toggle:
id: ${feedback_led_id}
transition_length: 0 s
- delay: ${feedback_blink_length}
- light.toggle:
id: ${feedback_led_id}
transition_length: 0 s
Installation
This device is not pre-flashed and not commercially available.
To use it you must:
- Build the hardware yourself
- Flash the ESP32 using ESPHome
- Configure and calibrate dimming parameters
Refer to the project documentation for detailed build and setup instructions.