Hello everyone! I wanted to share my completed workshop lighting project. It took me about two weekends to build and I’m very happy with the result.
Hardware:
- ESP32 (ESP-WROOM-32)
- rbdimmer 4-channel module
- Home Assistant with ESPHome integration
4 lighting zones:
- Main overhead fluorescent — runs at 60% most of the time
- Workbench task light — 100% when I’m working
- Inspection lamp — variable, controlled by HA slider
- Ambient LED strip — mood lighting, usually 20-40%
ESPHome config (key parts):
output:
- platform: ac_dimmer
id: ch1_overhead
gate_pin: GPIO18
zero_cross_pin: GPIO19
method: leading
- platform: ac_dimmer
id: ch2_workbench
gate_pin: GPIO21
zero_cross_pin: GPIO19
- platform: ac_dimmer
id: ch3_inspection
gate_pin: GPIO22
zero_cross_pin: GPIO19
- platform: ac_dimmer
id: ch4_ambient
gate_pin: GPIO23
zero_cross_pin: GPIO19
light:
- platform: monochromatic
output: ch1_overhead
name: \Workshop Overhead\"
default_transition_length: 2s
- platform: monochromatic
output: ch2_workbench
name: \"Workbench Light\"
- platform: monochromatic
output: ch3_inspection
name: \"Inspection Lamp\"
- platform: monochromatic
output: ch4_ambient
name: \"Ambient Strip\"
All 4 channels share the same zero-cross pin (GPIO19) — this is correct because the rbdimmer 4CH module has one shared ZC signal for all channels.
Home Assistant automations:
- Sunset: fade all lights to 20% over 10 minutes
- Motion sensor: restore workbench and overhead to 80% instantly
- 06:00 weekdays: fade overhead from 0% to 70% over 5 minutes (my workshop doubles as morning coffee spot)
Everything has been running stable for about 3 weeks now. Happy to answer questions if anyone wants to build something similar!"
Nice project Marcus! Clean setup with the 4CH module.
One question — did you add any EMI filtering on the signal lines between the ESP32 and the dimmer module? I’ve seen cases where the fast switching edges from the TRIAC can couple noise back onto the GPIO lines, especially with longer wire runs. At higher loads the noise can be enough to cause spurious zero-cross detections.
In my own 2-channel setup I added ferrite beads on the DIM and ZC lines (about 600Ω at 100MHz — standard 0805 ferrite like BLM21PG601SN1) and it cleaned up some jitter I was seeing on the ZC signal. Might be overkill for your setup if the wires are short, but worth mentioning.
Good point Ola! I actually did add ferrite beads on all signal lines — DIM1 through DIM4 and the ZC line. I used some generic snap-on ferrite clamps I had lying around from old USB cables. Not as precise as your SMD solution, but they seem to work.
The wire run between the ESP32 and the dimmer module is about 15cm, so quite short. I haven’t noticed any flickering or instability at any dimming level.
I don’t have an oscilloscope unfortunately, so I can’t give you measurements of the ZC signal quality. But I can tell you that in 3 weeks of operation I haven’t seen a single log entry from ESPHome about missed zero-crossings or timing issues. All 4 channels dim smoothly from 0-100% with no visible steps or flicker.
If the ferrite beads are doing their job, it makes no sense for me to change them for something fancier.
15cm is very short — you probably wouldn’t see issues even without the ferrites at that distance. The problems I encountered were with ~40cm runs in a control cabinet. At 15cm the parasitic inductance and capacitive coupling are minimal.
The snap-on ferrites are fine for this. They add common-mode impedance which is exactly what you want for rejecting coupled noise. No need for anything more elaborate.
3 weeks of clean logs is a good sign. If ZC jitter were an issue, you’d see it as occasional flicker at low dimming levels (below 10%) — that’s where timing errors are most visible because the firing window is very narrow.
Great project writeup Marcus! I have a similar 4-zone setup in my home lab.
One thing I’d ask about: what happens when Home Assistant goes offline? In my experience the HA server occasionally restarts for updates, and during that window the ESP32 loses its automation controller. What does your ESP32 do in that scenario — do the lights just freeze at their last state, or do they drop to zero?
This was a pain point in my early setup. The lights would go to 0% during HA restarts because ESPHome’s default behavior is to restore the output to its boot state, which for ac_dimmer is off.
Good question Hank — I spent some time on this because I had the same problem during my first HA update after installing the system.
Here’s my fallback configuration in ESPHome:
esphome:
on_boot:
priority: -100
then:
- light.turn_on:
id: overhead_light
brightness: 60%
- light.turn_on:
id: workbench_light
brightness: 100%
wifi:
on_disconnect:
then:
- logger.log: \WiFi disconnected — holding current light state\"
api:
on_client_disconnected:
then:
- logger.log: \"HA disconnected — lights will hold current state\"
The key insight: ESPHome by default keeps the last output state when the API client (HA) disconnects. You don’t need to do anything special for the "hold current state" behavior — it’s the default.
The on_boot section is the important part — it sets safe default brightness levels when the ESP32 itself restarts (power outage, OTA update, crash). Without this, the lights start at 0% after a reboot.
I set overhead to 60% and workbench to 100% as boot defaults because those are the levels I need most often. The inspection lamp and ambient strip start at 0% on boot — I control those manually anyway."
That’s a clean approach. My setup is similar but I added one extra piece — a restore_from_flash option so the ESP32 saves the last known brightness to flash memory periodically:
esphome:
on_boot:
priority: -100
then:
- delay: 5s
- if:
condition:
api.connected:
then:
- logger.log: \HA connected — normal operation\"
else:
- logger.log: \"HA not available — restoring saved state\"
- light.turn_on:
id: lab_overhead
brightness: !lambda 'return id(saved_brightness);'
globals:
- id: saved_brightness
type: float
restore_value: yes
initial_value: '0.6'
This way after a power outage the lights come back to whatever they were set to before, rather than a hardcoded default. The restore_value: yes on the global tells ESPHome to save it to flash.
For anyone finding this later: the delay: 5s on boot gives the WiFi time to connect before checking if HA is available. Without it, the condition always evaluates to false because WiFi hasn’t connected yet."
That’s clever with the restore_from_flash approach! I might adopt that. The hardcoded defaults work for me now, but your solution is more flexible — especially if I start changing my lighting scenes more often.
The 5-second delay tip is a good catch. I had exactly that issue in an earlier version where the boot script ran before WiFi was ready.
Thanks for the discussion everyone. If anyone else has built a multi-zone dimming project with rbdimmer modules, I’d love to see your setups — especially if you’ve done anything creative with automations or physical controls. Feel free to share in this thread or start a new one in the Projects category!