TL;DR: Install
rbdimmerESP32(for ESP32) orRBDdimmer(for ESP8266) plusPubSubClient. Connect to your MQTT broker, subscribe to a brightness topic, and callrbdimmer_set_level()in the callback. Publish back the current level as the state topic.
How It Works
ESP32 ──WiFi──► MQTT broker ◄────── Home Assistant / Node-RED
│ │
│ subscribe: dimmer/level/set
│ publish: dimmer/level/state
│
└──ISR──► TRIAC gate ──► Load (lamp, heater…)The ESP32 subscribes to a command topic. When HA publishes a value (0–100), the callback applies it to the dimmer. The ESP32 also publishes its current level so HA stays in sync.
Hardware
| Part | Example |
|---|---|
| MCU | ESP32 (dual-core) or ESP8266 |
| Dimmer | Any 1CH/2CH/4CH rbdimmer module |
| ZC pin | GPIO 5 (any GPIO on ESP32) |
| DIM pin | GPIO 4 (any GPIO on ESP32) |
| VCC | 3.3V |
⚠️ Use
rbdimmerESP32on dual-core ESP32. The olderRBDdimmerlibrary has noIRAM_ATTRon its ISR — it crashes when WiFi is active.rbdimmerESP32pins the ISR to Core 0; WiFi runs on Core 1. See: Wrong Library: RBDdimmer vs rbdimmerESP32
Required Libraries
Install via Arduino Library Manager or PlatformIO:
| Library | Purpose | Board |
|---|---|---|
rbdimmerESP32 |
AC dimmer ISR control | ESP32 only |
RBDdimmer |
AC dimmer ISR control | Arduino, ESP8266 |
PubSubClient |
MQTT client | Any |
WiFi.h |
WiFi (built-in) | ESP32 |
ESP8266WiFi.h |
WiFi (built-in) | ESP8266 |
PlatformIO (platformio.ini):
lib_deps =
https://github.com/robotdyn-dimmer/rbdimmerESP32
knolleary/PubSubClient @ ^2.8Full Code — ESP32
// AC Dimmer + MQTT on ESP32 (dual-core)
// Library: rbdimmerESP32 + PubSubClient
// IMPORTANT: use rbdimmerESP32, NOT RBDdimmer, on ESP32
#include <WiFi.h>
#include <PubSubClient.h>
#include "rbdimmerESP32.h"
// ── Configuration ─────────────────────────────────────────────
const char* WIFI_SSID = "YourSSID";
const char* WIFI_PASS = "YourPassword";
const char* MQTT_SERVER = "192.168.1.100"; // broker IP
const int MQTT_PORT = 1883;
const char* DEVICE_ID = "dimmer_esp32_01"; // unique per device
// MQTT topics
const char* TOPIC_SET = "dimmer/level/set"; // receive command
const char* TOPIC_STATE = "dimmer/level/state"; // publish state
// Dimmer pins
#define ZC_PIN 5
#define DIM_PIN 4
// ── Globals ───────────────────────────────────────────────────
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
rbdimmer_channel_t dimmerCh;
int currentLevel = 0;
// ── MQTT callback ─────────────────────────────────────────────
void onMessage(char* topic, byte* payload, unsigned int length) {
char buf[8];
int len = min((int)length, 7);
memcpy(buf, payload, len);
buf[len] = '\0';
int level = atoi(buf);
level = constrain(level, 0, 100);
rbdimmer_set_level(dimmerCh, level);
currentLevel = level;
// Publish confirmed state
char stateStr[8];
itoa(level, stateStr, 10);
mqtt.publish(TOPIC_STATE, stateStr, true); // retained
}
// ── MQTT reconnect ────────────────────────────────────────────
void mqttReconnect() {
while (!mqtt.connected()) {
Serial.print("Connecting to MQTT…");
if (mqtt.connect(DEVICE_ID)) {
Serial.println(" connected.");
mqtt.subscribe(TOPIC_SET);
// Republish retained state on reconnect
char stateStr[8];
itoa(currentLevel, stateStr, 10);
mqtt.publish(TOPIC_STATE, stateStr, true);
} else {
Serial.print(" failed, rc=");
Serial.println(mqtt.state());
delay(5000);
}
}
}
// ── Setup ─────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
// Connect WiFi
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500); Serial.print(".");
}
Serial.println("\nWiFi connected: " + WiFi.localIP().toString());
// Init dimmer
rbdimmer_init();
rbdimmer_register_zero_cross(ZC_PIN, RBDIMMER_PHASE_DEFAULT);
rbdimmer_config_t cfg = {
.gpio_pin = DIM_PIN,
.phase = RBDIMMER_PHASE_DEFAULT,
.initial_level = 0,
.curve_type = RBDIMMER_CURVE_RMS
};
rbdimmer_create_channel(&cfg, &dimmerCh);
// Setup MQTT
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setCallback(onMessage);
}
// ── Loop ──────────────────────────────────────────────────────
void loop() {
if (!mqtt.connected()) mqttReconnect();
mqtt.loop();
}Full Code — ESP8266
For ESP8266, swap the library and WiFi header. The MQTT logic is identical.
// AC Dimmer + MQTT on ESP8266 — complete sketch
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <RBDdimmer.h>
const char* WIFI_SSID = "YourSSID";
const char* WIFI_PASS = "YourPassword";
const char* MQTT_SERVER = "192.168.1.100";
const int MQTT_PORT = 1883;
const char* DEVICE_ID = "dimmer_esp8266_01";
const char* TOPIC_SET = "dimmer/level/set";
const char* TOPIC_STATE = "dimmer/level/state";
#define ZC_PIN 5 // D1 (NodeMCU)
#define DIM_PIN 4 // D2
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
dimmerLamp dimmer(DIM_PIN, ZC_PIN);
int currentLevel = 0;
void onMessage(char* topic, byte* payload, unsigned int length) {
char buf[8];
int len = min((int)length, 7);
memcpy(buf, payload, len);
buf[len] = '\0';
int level = constrain(atoi(buf), 0, 100);
dimmer.setPower(level);
currentLevel = level;
char stateStr[8];
itoa(level, stateStr, 10);
mqtt.publish(TOPIC_STATE, stateStr, true);
}
void mqttReconnect() {
while (!mqtt.connected()) {
if (mqtt.connect(DEVICE_ID)) {
mqtt.subscribe(TOPIC_SET);
} else {
delay(5000);
}
}
}
void setup() {
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
dimmer.begin(NORMAL_MODE, ON);
dimmer.setPower(0);
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setCallback(onMessage);
}
void loop() {
if (!mqtt.connected()) mqttReconnect();
mqtt.loop();
}MQTT Topic Structure
| Topic | Direction | Value | Example |
|---|---|---|---|
dimmer/level/set |
→ ESP32 | 0–100 |
75 |
dimmer/level/state |
← ESP32 | 0–100 |
75 |
For multiple dimmers, use unique topic prefixes per device:
dimmer/kitchen/level/set
dimmer/bedroom/level/setHome Assistant Configuration
Add a MQTT light to configuration.yaml (or use MQTT Discovery):
mqtt:
light:
- name: "Living Room Dimmer"
unique_id: "dimmer_esp32_01"
command_topic: "dimmer/level/set"
state_topic: "dimmer/level/state"
brightness_command_topic: "dimmer/level/set"
brightness_state_topic: "dimmer/level/state"
brightness_scale: 100
on_command_type: "brightness"
retain: true
optimistic: false
on_command_type: "brightness"— when you turn the light on from HA, it sends the brightness value as the MQTT payload (e.g.,75) instead of the string"ON". The firmware receives a number directly and applies it to the dimmer.
After adding, restart HA. The dimmer appears as a dimmable light entity with a 0–100% brightness slider.
Multi-Channel MQTT
For 2CH or 4CH modules, create additional topic pairs and channel handles. Each channel gets its own subscribe/publish pair:
// Additional channel
rbdimmer_channel_t ch2;
const char* TOPIC_SET2 = "dimmer/ch2/level/set";
const char* TOPIC_STATE2 = "dimmer/ch2/level/state";
// In onMessage(): check topic, route to correct channel
if (strcmp(topic, TOPIC_SET) == 0) {
rbdimmer_set_level(dimmerCh, level);
} else if (strcmp(topic, TOPIC_SET2) == 0) {
rbdimmer_set_level(ch2, level);
}Related Articles
- Wrong library on ESP32 → RBDdimmer vs rbdimmerESP32
- ESP32 crashes with WiFi → IRAM_ATTR Causes and Fix
- Single-core ESP32 → ESP32-S2/C3/H2 Doesn't Work
- ESPHome instead of MQTT → ESPHome YAML for AC Dimmer
- Which dimmer module → Complete Buyer's Guide
Still have questions?
Ask on forum.rbdimmer.com or open a GitHub Issue.