Tutorial, Project, Arduino, IoT, Christmas

It's the Most Wonderful Time of the Year ... for an IoT Relay Project!

Last weekend, my family decked the halls with boughs of holly - well, maybe not with holly, but we still decked us some halls nonetheless.

Anyway, as we were putting up our trees, I was thinking about how awesome it would be to remotely control the lights on each of them.

Yes, I know I can just go out and buy a remote controlled outlet, or maybe even an IoT Power Relay from my friends at SparkFun (which, by the way, is on my wish list), but I wanted to be able to build something myself with parts I already had, if at all possible.

Turns out, it was possible, and here's how I did it.

THE MICROCONTROLLER

My goal for this project was to use my existing T.I.T.U.S. infrastructure, so that I didn't have to build out too many extra pieces to make it all work. That, and I thought it would be cool to operate the lights using my voice instead of an app of some sort.

I already have a speech recognition component built out and running on a Raspberry Pi 3, so I figured I could just add a couple of extra commands to that unit to control the lights.

Next, I needed to hunt down a microcontroller that I could hook up to a relay that would switch the lights on and off. Being that T.I.T.U.S. passes messages to each component in the system via a RabbitMQ message bus hosted on AWS, I also needed the microcontroller to be able to connect to my WiFi (or network via Ethernet) and consume messages from my RabbitMQ server using AMQP or MQTT.

I had two Feather HUZZAH boards with WiFi modules that I received in a couple of different AdaBoxes, and neither one was in use, so I figured they would be a perfect fit for the project:

I created some sketches for various projects over the summer that utilized one of the MQTT Arduino libraries, so I figured I could just copy one of those, modify it to accept "on" and "off" commands for the relay, and then load it onto a HUZZAH board.

Bingo bango.

THE RELAY

Next, I needed to track down a relay I could use. Luckily, I had two 4-channel solid state relay modules made by SainSmart lying around from a previous project that ended up not being needed, so I figured those would work just fine.

Granted, I really only needed a single channel on each relay module for each tree, but being that these were all I had, they would have to do:

Whenever using relays, there are a few 'gotchas' that you need to keep in mind.

First, you need to understand the power requirements for the module. The modules I worked with in the past required a 5V power supply, and this module was no exception. However, since I was planning on using a HUZZAH board, I was going to come up short on this requirement, because the HUZZAH maxes out at 3.3V.

Digging a bit, I realized that you only need to supply 5V to the relay module for the case where all channels are switched on at the same time. Since I was planning on only using a single channel on the module, I kept my fingers crossed that 3.3V would be sufficient to power the channel (spoiler alert: it was).

In order to actually control, or switch, a channel, you need a separate input control voltage (using one of the GPIO pins on the HUZZAH). For this particular module, that voltage needs to be at least 2.5V to switch the channel from "off" to "on". Since the logic pins on the HUZZAH spit out 3.3V, I knew I was golden there, as well.

Next, you need to know if the relay is active HIGH or active LOW. What this means is that some relays trigger when they receive a LOW signal, but many trigger when they receive a HIGH signal. This bit me on a previous project, as I didn't realize I had purchased an active LOW relay module, so just keep an eye out for this.

If the product description doesn't indicate if it's active HIGH or LOW, then it's pretty easy to tell if you get everything wired up and the relay behaves in the opposite manner you expect. In that case, you just need to switch the signal you're feeding the relay to get the desired behavior.

You can work with either type, really, but from my experience, it's much easier to work with active HIGH relays, as many libraries and frameworks assume that's the type of relay with which you're working. The relay module used in this project is active HIGH, so it was easy to get it working like I wanted. With a little bit of know-how and tweaking, though, you can still get things to work properly with an active LOW relay.

The final thing I want to point out is that you need to check the current and load restrictions on the relay. Not only do you need to ensure your relay will work with AC or DC circuits (you need one that can switch AC circuits for this project, since the Christmas lights will be plugged into a wall outlet for power), but you also need to ensure that the relay can handle the current, or load, you'll be drawing.

For me, I was looking at drawing around 1/3 - 1/2 of an amp for a few light strands hooked end-to-end, and each channel on the relay module I was looking to use can handle up to 2 amps, so no problems there.

CREATING AN ADAPTER FOR THE LIGHTS

The last piece I needed to figure out was how to connect the lights to the relay. My initial thought was to just cut one of the wires on a strand on my tree, and then feed the split wires through the relay. But, I figured that wouldn't be the best route, because that strand of lights would then be useless without the relay (since it would have a split wire) or without splicing the wires back together.

What I really needed was a portable spliced light adapter, so to speak, that would plug into an outlet and that I could plug the lights on my tree into, as well.

To accomplish this, I needed to cut the plugs off of a separate strand of lights and splice them together. Of course, doing so would render that strand completely useless, as it would no longer have any plugs, but being that I had a bunch of strands left over from some synchronized light shows I did years ago, I figured I could spare some.

I took one strand of mini incandescent lights:

cut both plugs off of it:

stripped all four of the loose wires:

and then tied two of the stripped ends together to make an inline splice (NOTE: if you want to cover the spliced connection with heat-shrink tubing, you'll want to slide the tube over one of the wires first before tying them together):

After the wires were tied together, I soldered them a bit to make a stronger connection:

Finally, I slid the heat-shrink tubing over the joint and set it in place with a heat gun:

If you want more of a step-by-step tutorial on creating an inline splice, you might want to check out this guide on the Instructables site.

CONNECTING ALL OF THE PIECES

The final step (before adding the code, of course) was to wire everything together. Since my HUZZAH has stacking header pins protruding from the bottom of it, I just placed it on a breadboard for stability:

The breadboard serves no other purpose for this project, as I didn't wire anything up to the terminal strips.

To power the relay, I needed to add power supply and ground wires, so I connected the 3.3V pin on the HUZZAH to the D+ pin on the relay module and the GND pin on the HUZZAH to the D- pin on the relay module:

And, to actually control, or switch, the relay channel, I had to wire up one of the GPIO pins on the HUZZAH (I just arbitrarily chose pin 12) to the CH1 pin on the relay module:

I then took the light adapter I hacked together and connected the two stripped ends to the CH1 terminal on the relay module:

I plugged the male end of the light adapter into a wall outlet and then plugged a strand of Christmas lights into the female end of the adapter, so once it was all said and done, my setup looked like this:

THE HUZZAH CONSUMER CODE

The Feather HUZZAH board is Arduino-compatible, so for it, I needed to load an Arduino sketch with the appropriate ESP8266 and MQTT libraries onto the board:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "<your WiFi SSID here>";
const char* password = "<your WiFi password here>";

#define RABBITMQ_SERVER "<your RabbitMQ server address here>"
#define RABBITMQ_PORT <your RabbitMQ server port number here>
#define RABBITMQ_USERNAME "<your RabbitMQ username here>"
#define RABBITMQ_PASSWORD "<your RabbitMQ password here>"

WiFiClientSecure espClient;
PubSubClient mqtt(espClient);

void setup() {
    Serial.begin(115200);
    delay(100);
    
    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
    
    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected.");
    
    mqtt.setServer(RABBITMQ_SERVER, RABBITMQ_PORT);
    mqtt.setCallback(callback);
    
    pinMode(12, OUTPUT);
    digitalWrite(12, LOW);
}

void loop() {
    reconnect();
    mqtt.loop();
}

void reconnect() {
    // Loop until we're reconnected
    while (!mqtt.connected()) {
        Serial.print("Attempting MQTT connection...");
        // Attempt to connect
        if (mqtt.connect("Mini Tree", RABBITMQ_USERNAME, RABBITMQ_PASSWORD)) {
            Serial.println("connected");
            mqtt.subscribe("home.lights");
        } else {
            Serial.print("failed, rc=");
            Serial.print(mqtt.state());
            Serial.println(" try again in 5 seconds");
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
}

void callback(char* topic, byte* payload, unsigned int length) {
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    String response = "";
    for (int i = 0; i < length; i++) {
        response += (char)payload[i];
    }
    
    Serial.print(response);
    Serial.println();

    handleResponse(response);
}

void handleResponse(String response) {
    response.toLowerCase();
    
    if (response == "off") {
        digitalWrite(12, LOW);
    } else if (response == "on") {
        digitalWrite(12, HIGH);
    }
}

The code may look a little daunting at first, but if you pull out all of the comments and Serial output, it's really not too bad. I pieced this code together from a number of tutorials I've read over time, which used a lot of Serial output, so that's where a lot of that comes from.

Now, the first thing to do is to load in the necessary libraries needed for the project:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

The ESP8266WiFi header, as you probably guessed, defines the necessary components for getting connected to your WiFi. The PubSubClient header listed next pulls in the necessary components for connecting to a message broker using the MQTT protocol.

The next couple of lines just set up variables to hold WiFi credentials, and then the four lines following those define the necessary variables for getting connected to a RabbitMQ message broker:

const char* ssid = "<your WiFi SSID here>";
const char* password = "<your WiFi password here>";

#define RABBITMQ_SERVER "<your RabbitMQ server address here>"
#define RABBITMQ_PORT <your RabbitMQ server port number here>
#define RABBITMQ_USERNAME "<your RabbitMQ username here>"
#define RABBITMQ_PASSWORD "<your RabbitMQ password here>"

After all of the necessary connection variable declarations, I construct a new PubSubClient object that takes in a WiFiClientSecure object for connecting to the RabbitMQ server over HTTPS:

WiFiClientSecure espClient;
PubSubClient mqtt(espClient);

The setup block simply just does the initialization steps of connecting to the WiFi, setting up the server information for the MQTT client, defining a callback function for when a message is received/consumed, and then defining GPIO pin #12 as an output pin with an initial value of LOW:

void setup() {
    Serial.begin(115200);
    delay(100);

    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
    }

    mqtt.setServer(RABBITMQ_SERVER, RABBITMQ_PORT);
    mqtt.setCallback(callback);
    
    pinMode(12, OUTPUT);
    digitalWrite(12, LOW);
}

Pin #12, if you recall from earlier in the article, is used to switch the relay channel on and off, so initially, I write a LOW value to the pin to ensure the relay is switched off (it just seems like the safe thing to do).

Next up is the loop function, which really doesn't have a whole lot going on:

void loop() {
    reconnect();
    mqtt.loop();
}

All it really does is call the reconnect function to connect to the message broker, and then once a connection has been established, it tells the client to start consuming messages from the RabbitMQ server.

It may look like the reconnect function is doing a lot, but again, after stripping out all of the comments and Serial output lines, it's really not too bad:

void reconnect() {
    while (!mqtt.connected()) {
        if (mqtt.connect("Mini Tree", RABBITMQ_USERNAME, RABBITMQ_PASSWORD)) {
            mqtt.subscribe("home.lights");
        } else {
            delay(5000);
        }
    }
}

The function sets up a loop that checks whether or not the MQTT client is connected to the server. If a connection has already been established, then it won't even enter the loop body, and will exit the function immediately.

If, on the other hand, the client is not already connected, it calls the connect function with a client Id ("Mini Tree") and the credentials for connecting to the RabbitMQ server. Once connected, the client subscribes to the "home.lights" topic, as this is where the messages/commands will be published to control the lights.

If a connection cannot be made to the server due to the server being down, network issues, etc., then the else block is hit and a delay of 5 seconds is introduced before trying to connect to the server again.

Next up is the callback function, which is called when a message is received from the message broker:

void callback(char* topic, byte* payload, unsigned int length) {
    String response = "";
    for (int i = 0; i < length; i++) {
        response += (char)payload[i];
    }

    handleResponse(response);
}

The first argument passed to this function is the topic on which the message was received, the second argument is a series of bytes containing the actual message, and then the length argument indicates how many bytes are in the payload.

All this function is doing is looping through the bytes passed into the payload parameter and appending them onto a response string to piece together the content of the actual message received. In this case, the payload should contain either an "on" or "off" string to indicate whether the relay should be switched on or off.

The response string is then passed to the final function, handleResponse, which contains the little bit of code that actually triggers the relay:

void handleResponse(String response) {
    response.toLowerCase();

    if (response == "off") {
        digitalWrite(12, LOW);
    } else if (response == "on") {
        digitalWrite(12, HIGH);
    }
}

Since I want to compare the response string to "on" and "off" values, I first convert all of the characters to lowercase, as I can't guarantee that the casing of the commands/messages will be consistent. One publisher may send the message in all uppercase characters, one may use Pascal casing, and another publisher may use yet a different casing.

Yes, you can argue that I'm in control of what gets published, so I can just make sure that the publisher always sends the message in all lowercase characters, which would allow me to remove this line, but that's not really coding very defensively, so I'm just going to leave it.

Now that I've over-explained that line of code, the rest of the function just compares the message received with "off" and "on" values, and then writes a LOW value to pin #12 if the message is "off" and a HIGH value to the pin if the message is "on".

WRAPPING UP

At this point, you have the consuming code and instructions on how to make the IoT relay device itself, so you can really create any type of publishing app or device that you'd like, as long as it publishes to the same topic on which the consuming code is listening ("home.lights", per the reconnect function above).

Have fun with this, be safe, and go forth and create some awesome IoT Christmas light experience goodness. After all, 'tis the season.

Author image

About Tony Thorsen

Father of two, husband of one, Maker of many things. Tinkerer, dreamer, pixel nudger.