Building a DIY smart LED light dimmer with TRIAC and ESP32 allows you to add smooth, app-controlled dimming to any room in India without replacing existing LED bulbs. While not all LED bulbs support dimming, most quality Indian LED brands (Philips, Syska, Bajaj LED) offer dimmable variants, and this project gives you full control over brightness via phone, voice assistant, or automated schedules. This guide covers the complete TRIAC dimmer circuit design with phase-angle control, ESP32 firmware, and Home Assistant integration.
Table of Contents
- How LED Dimming Works
- Dimmable LEDs Available in India
- Components Required
- TRIAC Dimmer Circuit
- Zero-Crossing Detection
- ESP32 PWM Dimmer Code
- Home Assistant Light Entity
- Frequently Asked Questions
How LED Dimming Works
There are two methods for dimming LED lights:
1. Phase-Angle (TRIAC) Dimming – Traditional Method
A TRIAC (Triode for Alternating Current) switches the AC power on and off within each mains cycle (50Hz in India = 50 cycles/second). By delaying the firing of the TRIAC after each zero-crossing, it controls what portion of each AC half-cycle reaches the lamp. More delay = lower brightness. This is the method used in traditional dimmer switches.
Limitation: Works best with resistive or inductive loads (incandescent, halogen). LED drivers must explicitly support TRIAC dimming.
2. PWM Dimming – Modern Method
Switch the entire AC circuit on/off at high frequency using a relay or solid-state switch. Or better, use a 12VDC LED strip with PWM directly from the ESP32. PWM dimming works natively with all LED strips and most 12V LED drivers.
This tutorial covers TRIAC dimming for ceiling-mounted LED bulbs and 0-10V dimming for LED drivers.
Dimmable LEDs Available in India
Not all LED bulbs sold in India support TRIAC dimming. Look for “dimmable” label:
- Philips Dimmable LED Bulbs — most reliable for TRIAC compatibility
- Syska Dimmable LED — widely available in 7W-15W
- Havells Dimmable LED — good quality, sold at electrical shops
- Amazon Basics Dimmable LED — budget option with decent compatibility
Non-dimmable LEDs (which most cheap Indian LEDs are) will flicker, buzz, or not dim smoothly with TRIAC dimmers. Always buy explicitly labelled dimmable bulbs for this project.
Components Required
- ESP32 DevKit V1
- BT136-600E TRIAC (or equivalent 600V, 4A triac)
- MOC3021 or MOC3043 optocoupler (with zero-crossing detector)
- 4N25 or PC817 optocoupler (for ZC detection from ESP32)
- Resistors: 360 ohm, 22 ohm, 100 ohm
- 10nF snubber capacitor (ceramic)
- HiLink HLK-PM05 5V power module (for ESP32 power from mains)
- Heatsink for TRIAC
- Enclosure (must be non-conductive, rated for mains voltages)
TRIAC Dimmer Circuit
MAINS SIDE (230V AC - DANGEROUS):
Live (L) ---+-- [TRIAC BT136] ---+--- Load (LED lamp)
| |
Neutral(N)--+--------------------+
TRIAC Gate Drive (via MOC3021 optocoupler):
[LED side of MOC3021] anode: via 360 ohm resistor from 3.3V
cathode: ESP32 GPIO (gate control signal)
[Triac side of MOC3021]: T1 to Live, T2 to TRIAC gate
Zero-Crossing Detector:
Live ---[100k, 2W]---[100k, 2W]--- 4N25 LED anode
4N25 LED cathode --- Neutral
4N25 Transistor Collector --- 3.3V via 10k pullup --- ESP32 ZC GPIO
4N25 Transistor Emitter --- GND
Snubber Circuit (prevents TRIAC misfiring):
39 ohm in series with 10nF capacitor
Connected between TRIAC T1 and T2
Zero-Crossing Detection
Zero-crossing detection tells the ESP32 exactly when the mains AC sine wave crosses zero volts. This is the reference point for TRIAC firing timing. At 50Hz (India), zero-crossings occur 100 times per second (every 10ms).
// Zero-crossing interrupt handler
volatile bool zeroCrossing = false;
unsigned long zcTime = 0;
void IRAM_ATTR zeroCrossISR() {
zeroCrossing = true;
zcTime = micros();
}
setup():
attachInterrupt(digitalPinToInterrupt(ZC_PIN),
zeroCrossISR, RISING);
ESP32 PWM Dimmer Code
#include <WiFi.h>
#include <PubSubClient.h>
#define ZC_PIN 18 // Zero crossing input
#define GATE_PIN 19 // TRIAC gate output
int brightness = 50; // 0-100%
bool dimmerOn = true;
void IRAM_ATTR zeroCrossISR() {
if (!dimmerOn || brightness == 0) {
digitalWrite(GATE_PIN, LOW);
return;
}
if (brightness == 100) {
digitalWrite(GATE_PIN, HIGH);
return;
}
// Phase angle delay: more delay = less brightness
// At 50Hz: 10ms per half-cycle = 10000 microseconds
// Delay range: ~1ms (full bright) to ~9ms (very dim)
int delayUs = map(brightness, 0, 100, 9000, 1000);
delayMicroseconds(delayUs);
digitalWrite(GATE_PIN, HIGH);
delayMicroseconds(100); // Gate pulse width
digitalWrite(GATE_PIN, LOW);
}
void callback(char* topic, byte* payload, unsigned int length) {
String msg = String((char*)payload, length);
if (String(topic) == "light/bedroom/set") {
// Parse JSON: {"state":"ON","brightness":128}
if (msg.indexOf("OFF") >= 0) {
dimmerOn = false;
} else {
dimmerOn = true;
int b = msg.substring(msg.indexOf(":")+1).toInt();
brightness = map(b, 0, 255, 0, 100); // HA sends 0-255
}
}
}
void setup() {
pinMode(ZC_PIN, INPUT);
pinMode(GATE_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, RISING);
WiFi.begin("YOUR_SSID", "YOUR_PASS");
// ... MQTT setup and connect
mqtt.subscribe("light/bedroom/set");
}
void loop() {
mqtt.loop();
// Publish current state every 5 seconds
static unsigned long lastPub = 0;
if (millis() - lastPub > 5000) {
mqtt.publish("light/bedroom/state",
dimmerOn ? "ON" : "OFF");
mqtt.publish("light/bedroom/brightness",
String(map(brightness, 0, 100, 0, 255)).c_str());
lastPub = millis();
}
}
Home Assistant Light Entity
# configuration.yaml
mqtt:
light:
- name: "Bedroom LED"
state_topic: "light/bedroom/state"
command_topic: "light/bedroom/set"
brightness_state_topic: "light/bedroom/brightness"
brightness_command_topic: "light/bedroom/set"
schema: template
command_on_template: >
{"state": "ON", "brightness": {{ brightness }}}
command_off_template: '{"state": "OFF"}'
state_template: >-
{% if value == 'ON' %}on{% else %}off{% endif %}
Once configured, the light appears in Home Assistant as a dimmable entity. The Lovelace light card shows a brightness slider. Voice commands work: “Hey Google, dim the bedroom light to 30%” or “Set bedroom light to 50%.”
Frequently Asked Questions
Will this dimmer work with all LED bulbs in India?
Only explicitly dimmable LED bulbs work with TRIAC dimmers. Non-dimmable bulbs (the vast majority of cheap Indian LEDs) will flicker, buzz, or fail prematurely. Always verify the bulb packaging states “dimmable” or look for the dimmer-compatible symbol before installation.
Do I need any special electrician certification to install this?
Working on mains voltage requires care and should ideally be verified by a licensed electrician. The dimmer module works at 230V AC and must be installed in a proper mains-rated enclosure. If you are not confident with high-voltage work, purchase a commercial smart dimmer switch (Wipro, Legrand, Havells) and integrate it with Home Assistant instead.
Will this dimmer work with ceiling fans?
Standard ceiling fans have inductive motors and require a different type of control (capacitive or PWM motor control) — not simple TRIAC phase-angle dimming. This dimmer is specifically for resistive/dimmable LED loads. For smart fan speed control, use a dedicated fan regulator module designed for inductive motor loads.
How smooth is the dimming compared to commercial dimmers?
With proper zero-crossing detection and refined phase-angle calculations, this dimmer provides smooth dimming with minimal flicker (20-30 steps). Commercial dimmers often have 256 steps but the visible difference between 128 and 256 steps is negligible to the human eye.
Add comment