Dimming an AC lamp via MQTT

Years ago I published a TRIAC dimmer that could be controlled by a simple microcontroller such as an arduino. Times have changed and right now it is of more importance to be able to control that lamp from a home automation system such as OpenHab, Homematic or NodeRed.

So, let’s have a look at the circuit first:
This is a rather classic circuit. It detects the zerocrossing on the grid and then a microcontroller ignites the TRIAC with a time delay that determines the brightness of the attached Lamp.  The full cycle is 10mS (at 50Hz)
A circuit like this is easy to make for very low cost (1-2 Euro).

However, if you shy away from soldering  such a circuit, there are ready made modules available as well:
You should not need to pay more than 3-4 USD for such a module. Anything above that is robbery

Software to control this dimmer would look something like this:


#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 for 60Hz gridfrequency

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 in loop = ");
  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 value freqStep is usually set at 75 or 78 with this type of circuits, which allows for 128 steps of dimming at 50Hz gridfrequency. I have set the value here purposely to 100 allowing for only 100 steps, which is convenient for use in OpenHAB as  the slider goes from 0-100%.
The calculation is as follows:

50Hz
As we have two zerocrossings per sine wave, the frequency of zerocrossings is 100Hz. Therefore the period between two zerocrossings is 10mSec is 10000uS.
So if you want to have 100 levels of dimming the steps you need to take are 10000/100=100uS

60Hz
The frequency of zerocrossings is 120Hz, therefore the period between two zerocrossings is 8.3mS is 8300uS. So if you want to have 100 levels of dimming, the steps you need to take are 8300/100=83uS -> freqStep=83

 

Advertisements

MQTT with the ENC28J60 Ethershield

My old ENC28J60 based shield surely has gathered some dust
My old ENC28J60 based shield surely has gathered some dust

While playing with MQTT on various ESP8266’s, I started to wonder if maybe I could do something with an old ENC28j60 shield and module I still had laying around and actually hardly ever used.

For the youngsters: The ENC28J60 Ethernetshield was the first shield to connect the Arduino with the internet. The major drawback of the chip was that it was lacking a stack, that subsequently had to be constructed in software. As a result it was kinda memory hungry. The initial version was without an SD card slot, the later version had an SD card slot. There were two libraries available: The Ethercard and the Ethershield library. The UIPEthernet library came later.
As far as I could figure out, the PubSubClient library that is needed for MQTT doesnt work with the original Ethershield/EtherCard libraries. It does work with the UIPEthernet library though (extended fork here). The UIPEthernet library is a smart piece of coding that made programs written for the W5100 Ethernetshield suitable to be used with the ENC28J60 Shield, simply by changing the included library. Ofcourse there is a price to pay for this, namely more memory consumption. There is also an MQTT client for the ENC28J60/Atmega328 based Nanode. The Ethercard seems to work with the EthercardMQTT library.

Anyway, there still is enough memory to read a DHT11 sensor an analog port and some switches. In my case those were 3 door contacts. The PubSubClient is the original from Knolleary, though normally I am more a fan of the fork by Imroy. For this example I have used the Mosquitto public broker but ofcourse any broker can be used. I have installed the Mosquitto broker on a local raspberry and I found that a call to (“raspberrypi.local”,1883) does not work, using the  local 192.168.1.xxx ip number does work though

MQTT-Output
MQTT-Output ‘Dicht’ means ‘Closed’

The program is rather ‘spartan’, to save space. Print statements have been removed or commented out after initial testing.

#include <UIPEthernet.h>
#include "PubSubClient.h"
#include "DHT.h"

#define CLIENT_ID       "UnoMQTT"
#define INTERVAL        3000 // 3 sec delay between publishing
#define DHTPIN          3
#define DHTTYPE         DHT11
bool statusKD=HIGH;//living room door
bool statusBD=HIGH;//front door
bool statusGD=HIGH;//garage door
int lichtstatus;
uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x05};

EthernetClient ethClient;
PubSubClient mqttClient;
DHT dht(DHTPIN, DHTTYPE);

long previousMillis;

void setup() {
pinMode(4,INPUT_PULLUP);
pinMode(5,INPUT_PULLUP);
pinMode(6,INPUT_PULLUP);
  // setup serial communication
  //Serial.begin(9600);
  // setup ethernet communication using DHCP
  if(Ethernet.begin(mac) == 0) {
    //Serial.println(F("Ethernet configuration using DHCP failed"));
    for(;;);
  }
  // setup mqtt client
  mqttClient.setClient(ethClient);
  mqttClient.setServer("test.mosquitto.org",1883);
  //mqttClient.setServer("192.168.1.xxx",1883); //for using local broker
  //mqttClient.setServer("broker.hivemq.com",1883);
  //Serial.println(F("MQTT client configured"));

  // setup DHT sensor
  dht.begin();
  previousMillis = millis();
}

void loop() {
  statusBD=digitalRead(4);
  statusGD=digitalRead(5);
  statusKD=digitalRead(6);
  lichtstatus = analogRead(A0);
  // check interval
  if(millis() - previousMillis > INTERVAL) {
    sendData();
    previousMillis = millis();
  }
  mqttClient.loop();
}

void sendData() {
  char msgBuffer[20];
  float h=dht.readHumidity();
  float t = dht.readTemperature();
  if(mqttClient.connect(CLIENT_ID)) {
   mqttClient.publish("hal/temp", dtostrf(t, 6, 2, msgBuffer));
   mqttClient.publish("hal/humid", dtostrf(h, 6, 2, msgBuffer));
   mqttClient.publish("hal/door", (statusBD == HIGH) ? "OPEN" : "DICHT");
   mqttClient.publish("hal/garage",(statusGD == HIGH) ? "OPEN" : "DICHT");
   mqttClient.publish("hal/kamer",(statusKD == HIGH) ? "OPEN" : "DICHT");
   mqttClient.publish("hal/licht", dtostrf(lichtstatus, 4, 0, msgBuffer));
 //hal=hallway, DICHT=Closed, kamer=room, licht=light
 }
}
The old ENC28J60 shield at work
The old ENC28J60 shield at work

Just for completeness sake, this sketch takes about 77% of memory. This same sketch for the WIZ5100 based Ethernet shield, with the  Ethernet.h library takes about 52% of memory. If you decide to adapt the sketch, be careful with altering the character strings. For instance, using the string “OPEN” 3 times is likely less memory consuming than having 3 different strings of the same or even shorter length. Obviously the ‘topic’ strings take a lot of space as well and if you were to shorten them to less meaningful names, you could add more sensors such as for instance a PIR sensor.

Should you copy the program from this  blog page, you may copy ‘stray characters’  that you have to delete. You can also download it here.
An updated version is found here.

Freeing up memory

Should you really be pressed for memory, there is a way to free up about 5K of Flash: Go to your /../sketchfolder/libraries/UIPEthernet-master/utility/uipethernet-conf.h  and open the uipethernet-conf.h file.
in that file you will see the following section:
udp

If you set UIP_CONF_UDP to ‘0’ you will save 5kB flash, by disabling UDP. However, if you use DHCP to connect to your router, you cannot disable UDP as the DHCP connection requires UDP. In that case you still can gain a bit of memory by reducing the UIP_UDP_CONNS.
An example of using a fixed address is this:

#include <UIPEthernet.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; 
//the IP address for the shield:
byte ip[] = { 192, 168, 1, 120 };

void setup()
{
 Ethernet.begin(mac, ip);
}
void loop() {}

A final warning… after I updated my libraries, including the Adafruit DHT library, I received an error on compiling this sketch. That disappeared when I returned it to version 1.2.1. I thought I was not using the Adafruit library but the Tillaert library, but DHT libraries come a dime a dozen so maybe my compiler linked in another than I thought.
(The main sketch is my adaptation of work I found on internet, but I think the original source is from Luca Dentella)