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.
Circuit
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
i=0;
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
Serial.begin(9600);
while (!Serial) {};
Serial.println(F("dimmer"));
Serial.println();
// 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: ");
Serial.println(Ethernet.localIP());
Serial.println();
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]);
//Serial.println(ip);
// setup mqtt client
mqttClient.setClient(ethClient);
mqttClient.setServer( "192.168.1.103", 1883); // <= put here the address of YOUR MQTT server
//Serial.println(F("MQTT client configured"));
mqttClient.setCallback(callback);
Serial.println();
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) {
sendData();
previousMillis = millis();
}
mqttClient.loop();
Serial.print("dim1 = ");
Serial.println(dim1);
}
void sendData() {
char msgBuffer[20];
if (mqttClient.connect(CLIENT_ID)) {
mqttClient.subscribe("home/br/sb");
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(topic);
Serial.print("] ");//MQTT_BROKER
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
Serial.println(payload[0]);
dim1=strPayload.toInt();
}
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:
Sitemap:
Slider item=ACDIMMER switchSupport
item:
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)