Friday, March 1, 2019

ESP8266 and MQTT

The ESP8266 is a wonderful kludge. It found exposure as a module (ESP-01) that could be added to a micro controller giving it WiFi connectivity. People eventually figured out it was a processor that was more powerful than the typical micro controller it was being added to. The ESP8266 is a 32bit RISC processor running at 80MHz. Eventually there were various development kits put together for it, including the Arduino IDE.

Looking at the Wikipedia page, you can see the device is available in multiple form factors, from small boards to full blown DevKits. The bare boards are designed to be added to various other projects. The Devkits are standalone systems that will typically have a USB port provided for programming, powering and dataIO.

For most of the work I have been doing in my home automation systems, I have been using the NodeMCU or WeMos clones. These are available from many vendors, including Sparkfun, Banggood and others. I have programmed them using the Arduino IDE. I have built little boxes for them using 3D printed boxes from designs that are already on Thingiverse.



Because these modules are so inexpensive, I am able to limit the complexity of the individual node. I have one node that is in my bedroom that only has a NodeMcu and audio oscillator. This will alert me when something is out of the ordinary in the house. If I go to bed and the garage door was left open, or if someone breaks into the house.

The most complex node is the thermostat I have in the sunroom. This node has a touch screen display for displaying the current temperature in the room, and allows setting the thermostat. It also has a temperature and humidity sensor for measuring the conditions in the room.  There is a separate node that will turn on the heater if the temperature is too cold.

Coding for a node


Most nodes have a similar pattern. All the sensors in the node will send a payload on an MQTT topic. All the outputs in the node will listen for specific MQTT topics to take action on. Most of the nodes will only be action nodes (relays, lights, sound, etc), or sensor nodes (door open, temperature, switches, etc). Keeping the node simple will allow the system to be more reliable. Typically, nodes that do both input and output will not have input actions do output actions.

The node must know the MQTT agent to connect to. For my house, I am using a RaspberryPI that is acting as it's own WiFi hotspot. This will allow me to move the RaspberryPI and all the nodes on that network without affecting the home WiFi. The RaspberryPi doesn't need to be connected to the internet for this to work. I'll show in future posts how connecting it to the internet can be useful and safe. A spare Windows, Mac or Linux computer can be used as well, I just like the portability of the RaspberryPi.



A simple output node

/**
 * Simple action node. makes noise when receives alert
 * 
 * home/alert
 * 
 * They are separate devices, but using Node Red can be connected.
 * 
 * TGB 9/30/18
 */

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


const char* ssid = "raspi-webgui";
const char* password = "XXXXXXXX";
const char* MQTT_SERVER = "10.3.141.1";


WiFiClient espClient;
PubSubClient client(MQTT_SERVER, 1883, espClient);
PubSubClientTools mqtt(client);

String s = "";

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

  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  pinMode(D5, OUTPUT);
  
  // Connect to WiFi
  Serial.print(s+"Connecting to WiFi: "+ssid+" ");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(s+" connected with IP: "+WiFi.localIP());

  // Connect to MQTT
  Serial.print(s+"Connecting to MQTT: "+MQTT_SERVER+" ... ");
  if (client.connect("HomeAlert")) {
    Serial.println("connected");
    digitalWrite(BUILTIN_LED, HIGH); // turn LED off

    mqtt.subscribe("home/alarm", topic1_subscriber);
    mqtt.subscribe("alarm/light", topic2_subscriber);
  } else {
    Serial.println(s+"failed, rc="+client.state());
  }

}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("HomeAlert")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic","garageControl Reconnect");
      mqtt.subscribe("home/alarm", topic1_subscriber);
      mqtt.subscribe("alarm/light", topic2_subscriber);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" Resetting");
      // Wait 5 seconds before retrying
    }
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }

  client.loop();
}


void topic1_subscriber(String topic, String message) {
  Serial.println(s+"Message arrived in function 1 ["+topic+"] "+message);
  if (message.equals("on"))
  {
    tone(D5, 1000, 0);
  } else {
    noTone(D5);
  }
}

void topic2_subscriber(String topic, String message) {
  Serial.println(s+"Message arrived in function 1 ["+topic+"] "+message);
  if (message.equals("on"))
  {
    digitalWrite(BUILTIN_LED, LOW);   // Turn the LED on (Note that LOW is the voltage level
  }
  else
  {
    digitalWrite(BUILTIN_LED, HIGH);
  }
}



For Arduino programmers, this should look familiar. There are some definitions at the top, including the SSID, password for the WiFi network. The IP address of the MQTT server is also there. The global defines are next, where the wifiClient is defined, along with pubsub client definitions:


PubSubClient client(MQTT_SERVER, 1883, espClient);
PubSubClientTools mqtt(client);

The client is defined to use the MQTT_SERVER ip address, on port 1883 (normal MQTT server port). and will use the espClient WiFi client. The mqtt tools will use this client definition. I define a String s, but it is only used for debugging, and could be removed. 

Then the normal Arduino Setup function is defined. This function completes the setup. 
It defines the debugging serial port baud rate, and sets the pinModes to "output" for the built in LED and the in that the tone generator is connected to. Looking at the NodeMCU pinout, D5 is next to a ground so it makes a very easy interface, requiring only a 2 pin header (see picture above). The NodeMCU has constants for the labeled pins, so it isn't necessary to translate Arduino GPIOs to NodeMCU pins in your code. 

The WiFi connection is attempted until a connection is made, with a half second between tries. Lots of debug print when successful. The build in LED will be turned on when the NodeMCU is powered up, I turn the LED off once the WiFi connection is established. This helps when the node is away from a debug terminal. 

The MQTT client is used to connect next. Many examples aren't clear, but each node MUST have a unique name. If two nodes have the same name, the MQTT server will be confused and not know where to send messages to. Once the connection to the MQTT agent is established, subscriptions can be made. The topics are subscribed to along with the callback methods that should be invoked with a message on that topic is made available. This node will allow the LED to be controlled and the tone will be sounded. 

There is a reconnect method that will be nearly identical to the connect bits in the setup() function. The reconnect will connect if the client looses the connection, like with the MQTT server is rebooted. The main loop() method will check for a connection, and if not there, will call reconnect(). After the connection is restored, then the client.loop() function is called to see if any messages have arrived on subscribed topics. 

If a message arrives on any of the subscribed topics, one of the two callback methods is called. The topic1 messages will turn the sound on or off. The topic 2 messages will turn the LED on. The message that the callbacks check is the payload of the message. This node has simple payloads, only "on" or "off". Other nodes could have more sophisticated payloads. Even this node could have different frequencies for the tone as the payload to make a simple alert more useful (low tone for window open, higher tone for the garage open). 




This will build in the Arduino IDE using the "LOLIN(WEMOS) D1 R2 & mini" board setting in the Arduino IDE.  

It may not be the most efficient, but to run this node, I have a simple wall wart, and USB cable connected to the microUSB port on the board. It runs no trouble from a 500ma USB wall wart. Larger nodes may need more current. 

Next post, I'll start talking about using the MQTT server and how to control and hear from the nodes. 

No comments:

Post a Comment