Dimming a traditional incandescent lamp with OpenHAB

Tags: #<Tag:0x00007f61706a1f48> #<Tag:0x00007f61706a1de0> #<Tag:0x00007f61706a1b60>

Dimming lights has always been popular and there are a number of dimmable lamps that can be integrated with OpenHAB such as the Philips HUE, the IKEA Tradfri, the (now retired) Sonoff dimmable LED pack or a number of WiFi modules for RGB(W) strips.
Sometimes however all you want or need is to dim a classical incandesent lamp.

Traditionally these are regulated with TRIAC dimmers and many years ago I already built one that could be controlled by an Arduino.
Though I had some ideas to integrate that with OpenHAB, it was somewhere at the bottom of my list. A recent inquiry however from another community member, moved it up my list.
Anyway, I will start with a bit of theory on dimming incandescent lamps.
Incandescent lamps are best dimmed by phase cutting, that means that you have a TRIAC that is OFF during a specific time of the sinewave coming from the grid. Only when it is switched on, the lamp will light up, but depending where in the sinewave phase it is ignited, the energy provided and thus the intensity of the light will be higher or lower.

Now you cannot just randomly switch a TRIAC on and hope for the best. You need a specific time reference to know what amount of dimming you will get.
For that we use the zerocrossing. That is the moment that the sinewave goes through the zero point (and changes phase).

Imagine that you would ignite the TRIAC at the moment the sinewave goes through zero, the lamp will receive the full sine wave and will therefore burn at full brightness. If you would wait with igniting the TRIAC till the sinewave is at its peak (that is halfway), the lamp will only burn at half intensity. If you wait with igniting the TRIAC till right before the sinewave will go through zero again, you are almost at the end of the sinewave and the lamp will only burn at very low intensity.

So, hardware wise we only need a very simple circuit that detects the zerocrossing and combine that with a circuit that ignites a TRIAC.
Ofcourse we use optocouplers for that to galvanically separate us from the mains grid.

At this moment a word of warning: If you have no clue about working with mains voltage, do not build this. A mains grid voltage can kill you!!

Anyway, it is fairly easy to build such a circuit as pictured below. It will only cost you 1-2 euro. Nevertheless, I advise against building it yourself, as the asian webstores sell ready made modules for around 4 USD.


mind you, you do not really need LED1 at the input. That LED is only of use if you would use the circuit as a switch

DIY build

Commercial module
Next thing we need is a program that detects the zerocross signal coming from the module and then starts a timer.
If we are using a 50Hz grid signal, that means that the frequency of the zerocrosssignal (2 per sinewave) is 100Hz.
So the period between two zerocross signals is 10mSecs (or 10000uS).
So what the software needs to do is to detect the zerocrossignal, then wait a specific shorter (for high brightness) or longer (for low brightness) time, before igniting the TRIAC and the entire process should take maximum of 10mSecs before it starts all over again.

The waiting time is determined by a value that we MQTT to the software.

Though theoretically it would be easiest to use a ‘delay()’ command (and that might also help to better understand the process) that would put the processor in waiting mode most of the time and then it cannot do much else (like receive MQTT commands).
therefore we use a timer, so the processor can do what it needs to do until the timer pats it on the shoulder saying ‘time to ignite the TRIAC’
For that I have divided the mentioned 10mS (10000uS) in 100 steps of each 100uSecs.
So every 100uSecs the timer will interrupt, it will be checked against the dimming value and if not reached yet it will say: "Sorry for the interruption, please move on’. However, when it does reach the dimming value, it will say: “OK Switch on the TRIAC”

I have chosen those 100 steps because that is standard coming from the openHAB slider/dimmer. For hardware I use an Arduino UNO with an W5100 Ethernetshield

#include <Ethernet.h>
#include "PubSubClient.h"
#include  <TimerOne.h>

#define CLIENT_ID       "Dimmer"
#define PUBLISH_DELAY   3000

String ip = "";
bool startsend = HIGH;
uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x07};

volatile int i=0;               // Variable to use as a counter volatile as it is in an interrupt
volatile boolean zero_cross=0;  // Boolean to store a "switch" to tell us if we have crossed zero
int AC_pin1 = 4;// Output to Opto Triac
int dim1 = 0;// Dimming level (0-100)  0 = on, 100 = 0ff

int inc=1;                      // counting up or down, 1=up, -1=down
int freqStep = 100; //make this 83 if you are using 60Hz

EthernetClient ethClient;
PubSubClient mqttClient;
long previousMillis;

void zero_cross_detect() {    
  zero_cross = true;               // set the boolean to true to tell our dimming function that a zero cross has occured
  digitalWrite(AC_pin1, LOW);       // turn off TRIAC (and AC)

// Turn on the TRIAC at the appropriate time
void dim_check() {                   
  if(zero_cross == true) {              
    if(i>=dim1) {                     
      digitalWrite(AC_pin1, HIGH); // turn on light       
      i=0;  // reset time step counter                         
      zero_cross = false; //reset zero cross detection
    else {
      i++; // increment time step counter                     

void setup() {
  attachInterrupt(0, zero_cross_detect, RISING);    // Attach an Interupt to Pin 2 (interupt 0) for Zero Cross Detection
  Timer1.initialize(freqStep);                      // Initialize TimerOne library for the freq we need
  Timer1.attachInterrupt(dim_check, freqStep);
  pinMode(4, OUTPUT);

  // setup serial communication

  while (!Serial) {};

  // setup ethernet communication using DHCP
  if (Ethernet.begin(mac) == 0) {
    Serial.println(F("Unable to configure Ethernet using DHCP"));
    for (;;);

  Serial.println(F("Ethernet configured via DHCP"));
  Serial.print("IP address: ");

  ip = String (Ethernet.localIP()[0]);
  ip = ip + ".";
  ip = ip + String (Ethernet.localIP()[1]);
  ip = ip + ".";
  ip = ip + String (Ethernet.localIP()[2]);
  ip = ip + ".";
  ip = ip + String (Ethernet.localIP()[3]);

  // setup mqtt client
  mqttClient.setServer( "", 1883); // <= put here the address of YOUR MQTT server
  //Serial.println(F("MQTT client configured"));


  Serial.println(F("Ready to send data"));
  previousMillis = millis();
  mqttClient.publish("home/br/nb/ip", ip.c_str());

void loop() {

  // it's time to send new data?
  if (millis() - previousMillis > PUBLISH_DELAY) {
    previousMillis = millis();


    Serial.print("dim1  = ");

void sendData() {
  char msgBuffer[20];
  if (mqttClient.connect(CLIENT_ID)) {

    if (startsend) {
      mqttClient.publish("home/br/nb/ip", ip.c_str());
      startsend = LOW;

void callback(char* topic, byte* payload, unsigned int length) {
  char msgBuffer[20];
   payload[length] = '\0';            // terminate string with '0'
  String strPayload = String((char*)payload);  // convert to string
  Serial.print("strPayload =  ");
  Serial.println(strPayload); //can use this if using longer southbound topics
  Serial.print("Message arrived [");
  Serial.print("] ");//MQTT_BROKER
  for (int i = 0; i < length; i++) {



The Arduino pins used are pin 2 (interrupt 0) for the zerocross signal and pin 4 to trigger the TRIAC (via the optocoupler)>
The Software will also send some info to openhab, mainly the IP number

In OpenHAB:
Slider item=ACDIMMER switchSupport

Dimmer ACDIMMER "Dimmer [%d %%]" <light> {mqtt=">[mosquitto:home/br/sb:command:*:default]"}

Just a few final remarks.
Again, if you do not feel comfortable working with mains voltage, please stay away from this project. If you decide to make this and you are testing it, remember that the TRIAC and all components attached to it do carry a possibly lethal voltage. Do not feel if the TRIAC gets hot while it is still plugged into the mains.
Also, the zerocoss signal has a specific width. It starts slightly before the actual zerocross and ends a bit after the actual zerocross. This means that it could happen that if you regulate for full brightness (value 0) or full dim (value 100), you COULD get slight flickering. Tweaking the freqStep value should cure that.

In principle, this could be built with an ESP8266 as well, as that has interrupts on all its pins and it does have timers (stay away from the hardware timer, use the os_timer). Point is that I just havent tried that (I might in future)


Nice write up!!! To add, this is exactly the same concept used in dimming LEDs and/or controlling the speed of a PWM fan.

Yes, though with controlling the speed of a fan you would need to add at least a snubber circuit. TRIAC’s can switch inductive loads but are not especially suited for it.
With regard to dimmable LED’s… indeed there are LEDś that are suitable to be dimmed by a classical TRIAC dimmer, but not all dimmable LED’s can. Some need dedicated dimmers, so if you want to dim an LED with this, make sure it is suitable for classic TRIAC dimmers

Just my 2 cents because I’m just getting back into home automation, and I’ve been looking for dimming solutions, for existing lighting that works with current hardware and s/w. I’ve used X-10 dimming switches and fan control switches a while ago. With good luck, so I’m wondering if a simple R/F bridge to control the old X-10 communication would be a simple answer to the dimming of A/C and fan speed?? I personally am not far enough along implementing the new wifi and cloud based systems to test it, but I think I prolly have a drawer full of X-10 stuff that can maybe reincorporate in to the newer systems. And A/C dimming seems to be an issue.

1 Like

Absolutely, These devices are also safety rated so much safer than any DIY solution involving mains.
A number of users have integrated X10 devices into openHAB.
Although there is no direct X10 binding these devices are being used with OH
Search the forum

1 Like