TL;DR: Setting the dimmer to 0% does not guarantee the lamp turns off. TRIAC holding current keeps the switch conducting at low loads. Fix: call
dimmer.setState(OFF)orsetMode(OFF_MODE)instead ofsetPower(0)— or add a relay for full galvanic disconnection. DimmerLink stops all gate pulses at level 0, eliminating the issue in software.
What the Problem Looks Like
You call setPower(0) or set brightness: 0 in your automation. The
load should be off. Instead:
- an incandescent or halogen lamp continues to glow dimly;
- the lamp may flicker slowly (1–4 Hz) at "zero" level;
- the behavior is worse with low-wattage lamps (25–40 W) and disappears with 100 W+.
This is not a code error. It is a fundamental property of TRIAC circuits interacting with small resistive loads.
Root Causes
Cause 1 — TRIAC Holding Current
A TRIAC (Triode for Alternating Current) conducts in both AC half-cycles once triggered. To stop conducting it must naturally reach zero current crossing — and the current through the load at the moment of crossing must fall below the holding current (I_H) of the device.
Typical holding current for a BTA16 or BT138 TRIAC: 25–80 mA.
With a 25 W lamp at 230 V:
I_load = 25 W / 230 V ≈ 109 mA (peak during conduction)At a very small phase-cut angle (near 0%, i.e., near the zero-crossing) the current pulse is so brief that it collapses below I_H before the TRIAC has time to latch. Result: some half-cycles the TRIAC opens, some it doesn't — producing slow, irregular flicker or a constant dim glow.
Cause 2 — Optocoupler Leakage Current
The TRIAC driver optocoupler (MOC3041, MOC3021, or equivalent) passes a small leakage current (1–5 mA typical) even when the LED inside is off. For a 100 W lamp this is invisible. For a 10 W LED lamp or a 25 W incandescent the leakage alone may be enough to sustain a glow.
Cause 3 — Library Minimum Pulse at 0%
Both RBDdimmer and rbdimmerESP32 enforce a minimum non-zero firing
angle to avoid hardware instability near the crossings. Calling
setPower(0) may still generate a very short gate pulse each half-cycle.
The load therefore receives a tiny power burst — not enough to light a
100 W bulb, but visible on a 25 W lamp.
The solution is to switch the library mode to OFF, not just set power to 0.
Diagnosing the Problem
- Check the wattage — replace the 25–40 W lamp with a 100 W incandescent. If the glow disappears, holding current is the cause.
- Disconnect the MCU — unplug all control signals (DIM, ZC, VCC, GND on the MCU side). If the lamp still glows, leakage current is the cause; if it goes out, the library is sending residual pulses.
- Check the API call — search your code for
setPower(0). Confirm you are not missing asetState(OFF)call.
Solutions
🟢 For Beginners — Use DimmerLink or a Relay
DimmerLink manages gate firing in its own firmware (Cortex-M0+).
At level 0 it generates zero gate pulses — the TRIAC is not triggered
regardless of leakage. Write 0 to register 0x10 (DIM0_LEVEL) and
the lamp turns off completely.
// Arduino / ESP32 — DimmerLink I2C
Wire.beginTransmission(0x50);
Wire.write(0x10); // DIM0_LEVEL register
Wire.write(0); // 0% — no gate pulses
Wire.endTransmission();Add a relay if the load must be fully de-energised (galvanic isolation). Connect a normally-open relay (or SSR) in series with the dimmer's AC-IN. Open the relay when the load should be off; close it to enable dimming:
// Arduino example
const int RELAY_PIN = 7;
void setLoad(bool on, uint8_t level) {
if (!on) {
dimmer.setState(OFF);
digitalWrite(RELAY_PIN, LOW); // relay open — fully off
} else {
digitalWrite(RELAY_PIN, HIGH); // relay closed — enable AC path
delay(5); // let contacts settle
dimmer.setState(ON);
dimmer.setPower(level);
}
}🔵 For Advanced Users — Software and Hardware Fixes
Option A — Use the Correct OFF Mode (Software)
setPower(0) sets the firing angle to near-zero but may still generate
a pulse. The correct way to turn the load completely off is to change the
operating mode:
rbdimmerESP32 / RBDdimmer:
#include <rbdimmerESP32.h> // or RBDdimmer.h for AVR/ESP8266
rbdimmer::Dimmer dimmer(DIM_PIN, ZC_PIN);
void setup() {
dimmer.begin(NORMAL_MODE, ON);
dimmer.setPower(50);
}
void turnOff() {
// Correct: switch mode to OFF — no gate pulses at all
dimmer.setState(OFF);
// Wrong: dimmer.setPower(0) — may still fire
}
void turnOn(uint8_t level) {
dimmer.setState(ON);
dimmer.setPower(level);
}If your library version uses
setMode():
dimmer.setMode(OFF_MODE); // turns off
dimmer.setMode(NORMAL_MODE); // resumes dimmingESPHome ac_dimmer component:
ESPHome's ac_dimmer component sets output: false to fully inhibit
gate pulses:
output:
- platform: ac_dimmer
id: triac_output
gate_pin: GPIO4
zero_cross_pin: GPIO5
light:
- platform: monochromatic
output: triac_output
name: "Lamp"
# When HA sends "turn off", the component stops gate pulses.When Home Assistant sends a turn-off command, ESPHome stops gate pulses entirely — the TRIAC is not triggered and the lamp goes out.
Option B — RC Snubber (Hardware)
An RC snubber connected in parallel with the TRIAC reduces the rate of voltage rise (dV/dt) across the device after a zero crossing. This helps the TRIAC self-commutate reliably at small conduction angles.
TRIAC anode/cathode
┌──────────┐
│ TRIAC │
└──────────┘
│ ← in parallel: 100 Ω (0.5 W) + 100 nF class-X2
└── R ── C ── ┘Standard snubber values: R = 100 Ω (0.5 W) and C = 100 nF, class X2 (rated for mains voltage).
Most rbdimmer modules already include a built-in snubber. Check your module's schematic before adding an external one.
An RC snubber does not eliminate the problem completely — it makes TRIAC turn-off more reliable, but optocoupler leakage persists.
Option C — Series Relay for Full Disconnection
If the load must be completely de-energised (safety requirement, or inductive load), add a relay in series:
| Scenario | Relay type | Note |
|---|---|---|
| Normal loads | SPST NO relay | Open = no AC path |
| Inductive (motors) | SPST NO relay | Arc suppression recommended |
| Mains isolation required | SSR (solid state relay) | No moving parts |
Use the dimmer for power control and the relay for on/off:
void loop() {
if (targetLevel == 0) {
dimmer.setState(OFF);
digitalWrite(RELAY_PIN, LOW);
} else {
digitalWrite(RELAY_PIN, HIGH);
delay(5);
dimmer.setState(ON);
dimmer.setPower(targetLevel);
}
}Load Compatibility Matrix
| Load | Symptom at 0% | Root cause | Fix |
|---|---|---|---|
| Incandescent 100 W+ | No glow | Enough load | setState(OFF) sufficient |
| Incandescent 25–40 W | Faint glow | Holding current | setState(OFF) or relay |
| Halogen 50 W | Glow or slow flicker | Holding current | RC snubber + setState(OFF) |
| Halogen 12 V (transformer) | Glow | Reactive current | Relay for full disconnection |
| Heating element | No glow | Resistive load | setState(OFF) sufficient |
Common Mistakes
| Mistake | Effect | Fix |
|---|---|---|
Calling setPower(0) instead of setState(OFF) |
Residual pulse at each half-cycle | Use setState(OFF) |
| Assuming 0% = off for low-wattage lamps | Glow or flicker | Use OFF mode |
| Adding an undersized snubber (< 0.5 W resistor) | Snubber overheats | Use 0.5 W or 1 W resistor |
| Using a relay without arc protection on inductive loads | Contact welding | Add snubber across relay contacts |
Quick Checklist
Before replacing hardware, check these in order:
Related Articles
- Flickering at low brightness → AC Dimmer Flickering at Low Brightness
- Safety and wiring → AC Dimmer Safety: Mains Voltage, Isolation, and Wiring
- Choosing a dimmer → AC Dimmer Buyer's Guide
- LED-specific flicker → LED Flickering with AC Dimmer
Still have questions?
Ask on forum.rbdimmer.com or open a GitHub Issue.