Poor mans smart relay. Total cost of the whole setup is below $5. DIY smart wifi socket/switch/relay that you can control by your own phone or homeassistant or whatever uses mqtt.

Components

  1. Node MCU (I use LoLin v3.0)
  2. 2 Way Relay module
  3. Micro usb cable
  4. USB phone charger

Theory

The relay module have two physical relays and two corresponding input pins IN1 and IN2. When current is received at any of inputs the corresponding physical relay is open, and when the current flow stops the relay gets closed. As module specification says the input current should be 5V to open a relay, but the NodeMCUs GPIO out of 3.3V is still enough.

So for me it's enough to connect the two NodeMCU GPIO pins to relays IN1 and IN2, and write a program that will enable and disable power on GPIO pins when MQTT message received. Also It would be handy if this system will send an MQTT message with it's own status and open/closed relay statuses.

Hence I decided to use the Arduino IDE, I would need to install the libs for ESP8266, the wifi connectivity, and MQTT service client.

I. Setup of libs

At Arduino IDE File -> Preferences -> Settings -> Additional boards manager URLs add:

http://arduino.esp8266.com/stable/package_esp8266com_index.json"

Go to Tools -> Board -> Boards Manager type "ESP8266" and install it.

Now adding mqtt: Tools -> Manage Libraries -> serach for PubSubClient (https://pubsubclient.knolleary.net/) this is checked MQTT lib.

II. Connecting

So I need 2 GPIOs of NodeMCU. According to pinout sketch the pins D1 and D2 would suit me, they corresponds to the GPIO pins of number 5 and 4.

NodeMCU LoLin V3 pinout

So I connected D1 (or GPIO05) to realys IN1 it is the left relay.
Also, I connected D2 (or GPIO04) to relays IN2, it is the right relay.

Afterwards I connected 3V output and G (round) outputs of NodeMCU to the VCC and GND inputs of the relay module.

My NodeMCU connection
My relay connection

III. The code

So at the beginning we need to define our config variables:

const char* ssid = "";
const char* password = "";
const char* mqtt_server = "broker.mqtt-dashboard.com";

const int stateMessageEverySec = 5;

#define LEFT_SWITCH 4
const char* leftControlTopic = "urgn/switch/left/control";
const char* leftStateTopic = "urgn/switch/left/state";

#define RIGHT_SWITCH 5
const char* rightControlTopic = "urgn/switch/right/control";
const char* rightStateTopic = "urgn/switch/right/state";

There are such options to set:

  • ssid - the name of your wifi network.
  • password - the wifi password.
  • mqtt_server - address of an mqtt broker service.
  • LEFT_SWITCH and RIGHT_SWITCH pin numbers for connected relay IN1 and IN2.
  • leftControlTopic and rightControlTopic - the mqtt topics for controlling the switches.
  • leftStateTopic and rightStateTopic - the mqtt topics on which our switch will report is own state.
  • stateMessageEverySec - the rate in seconds of publishing state messages.

Now the full sketch code, function explanations will follow.

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

const char* ssid = "";
const char* password = "";
const char* mqtt_server = "broker.mqttdashboard.com";

const int stateMessageEverySec = 5;

#define LEFT_SWITCH 4
const char* leftControlTopic = "urgn/switch/left/control";
const char* leftStateTopic = "urgn/switch/left/state";

#define RIGHT_SWITCH 5
const char* rightControlTopic = "urgn/switch/right/control";
const char* rightStateTopic = "urgn/switch/right/state";

WiFiClient espClient;
PubSubClient client(espClient);

void setupWiFi() {
  delay(10);
  Serial.println(); Serial.print("Connecting to "); Serial.println(ssid);
  
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500); Serial.print(".");
  }
  
  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnectMQTT() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("urgn/testOut", "hello world");
      // ... and resubscribe
      client.subscribe(leftControlTopic);
      client.subscribe(rightControlTopic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void publishRelayState(const char* relayTopic, int relayCode) {
  char* message;
  
  if (digitalRead(relayCode) == HIGH) {
    message = "OFF";
  } else {
    message = "ON";
  }
  
  client.publish(relayTopic, message);
}

void processRelayCallback(const char* controlTopic, const char* stateTopic, int relayCode, char* topic, char* command) {
    if (strcmp(controlTopic, topic) == 0) {
     if (strcmp(command, "ON") == 0) {
        Serial.println("ON");
        digitalWrite(relayCode, LOW);
        publishRelayState(stateTopic, relayCode);
     } else if (strcmp(command, "OFF") == 0) {
        Serial.println("OFF");
        digitalWrite(relayCode, HIGH);
        publishRelayState(stateTopic, relayCode);
     } else {
         Serial.println("Unknown command");
     }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  payload[length] = '\0';
  char *cstring = (char *) payload;
  Serial.print(cstring);
  Serial.println();

  processRelayCallback(leftControlTopic, leftStateTopic, LEFT_SWITCH, topic, cstring);
  processRelayCallback(rightControlTopic, rightStateTopic, RIGHT_SWITCH, topic, cstring);
}

void setup() {
  pinMode(LEFT_SWITCH, OUTPUT); 
  pinMode(RIGHT_SWITCH, OUTPUT);
   
  Serial.begin(9600);
  
  setupWiFi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  digitalWrite(LEFT_SWITCH, HIGH);
  digitalWrite(RIGHT_SWITCH, HIGH);
}

unsigned long lastMsg = 0;
void loop() {
  if (!client.connected()) {
    reconnectMQTT();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastMsg > stateMessageEverySec * 1000) {
    lastMsg = now;
    publishRelayState(leftStateTopic, LEFT_SWITCH);
    publishRelayState(rightStateTopic, RIGHT_SWITCH);
  }
}

What each function does:

  • setupWiFi - function that connects to WiFi.
  • reconnect - connects to the MQTT.
  • processRelayCallback - function triggered when received an MQTT message, tooks relay configurations and checks the topic with message. If possible switches relay.
  • publishRelayState - function that tooks relay configuration and publish to MQTT its current status.
  • callback - general purpose function is triggered when any MQTT message received, triggers processRelayCallback for each relay.
  • setup - standard Arduino initialization function triggers setupWiFi,  subscribes to needed mqtt topics and binds pins.
  • loop - standard Arduino  reconnectMQTT with other port bindings.

IV. Testing

Since our setup is connected to the public mqtt server somewhere in the internet we can control it with some web activated mqtt clients. One of such clients is HiveMQ.

So, connect to the broker.mqttdashboard.com which basically is specified in code as value of mqtt_server.

Then subscribe to topics urgn/switch/left/state (`leftStateTopic` ) and urgn/switch/right/state (rightStateTopic).

Now you can control your switch by publishing messages to either urgn/switch/left/control (leftControlTopic) and urgn/switch/right/control (rightControlTopic) with payloads ON or OFF to control the switch.

Although you can see some loggin of your actions on Serial monitor of Arduino IDE.

V. Home assistant integration

I also have local mqtt server integrated with homeassistant. So I basically change my mqtt_server variable to adress of my local server. And add the following to the configuration.yml of home assistant.

switch:
  - platform: mqtt
    name: "switch left"
    icon: mdi:lightbulb-on
    command_topic: "urgn/switch/left/control"
    state_topic: "urgn/switch/left/state"
    payload_on: "ON"
    payload_off: "OFF"
    state_on: "ON"
    state_off: "OFF"
    optimistic: false
    qos: 0
    retain: false
  - platform: mqtt
    name: "switch right"
    unique_id: esp8266_01_right
    icon: mdi:lightbulb-on
    command_topic: "urgn/switch/right/control"
    state_topic: "urgn/switch/right/state"
    payload_on: "ON"
    payload_off: "OFF"
    state_on: "ON"
    state_off: "OFF"
    optimistic: false
    qos: 0
    retain: false

After restart of the service they should appear on the Entities.

So you can add nice tiles and control over your devices!

Happy coding!

https://create.arduino.cc/projecthub/najad/using-arduino-ide-to-program-nodemcu-33e899
https://github.com/knolleary/pubsubclient/blob/master/examples/mqtt_esp8266/mqtt_esp8266.ino
https://www.home-assistant.io/integrations/switch.mqtt/