MQTT: Building an Open Internet of Things

2646

esp8266The “Internet of Things” (IoT) is all about physical objects being able to communicate with each other. It may be that your home mailbox can tell you that new mail has arrived, windows know that it is raining and close themselves, or your washing machine knows that somebody just got in the shower and pauses itself temporarily. The MQ Telemetry Transport MQTT is an open protocol that allows devices to publish and subscribe to messages.

MQTT makes it fairly straightforward to set up programs on a Linux machine that harvest information and publish that info on the network for small, resource-constrained microcontrollers to see and process. The recent availability of very cheap WiFi-enabled microcontrollers such as the ESP8266 makes this an exciting time to be tinkering with IoT.

The advantage of using messages is that devices can listen for interesting things and can send any information that they think is important. Every device doesn’t need to know about the other devices on the network for this to happen. For example, a weather station can just publish the temperature, humidity, wind speed, and direction and the rest of your “things” can subscribe to take advantage of that information. Although there are many ways to send messages on a Linux desktop, MQTT should let you sent messages to and from your Arduino or mbed smart devices, too. If you are interested in buying an IoT or “smart” device, you might want to investigate whether the messaging used by it is an open standard, such as MQTT.

MQTT is published as an open standard by OASIS. Many implementations of MQTT are available, including the one I’ll focus on here: Mosquitto. Mosquitto can be installed on a Fedora 23 machine using the first command below and started with the second command.

# dnf install mosquitto-devel
# systemctl start mosquitto

Programs subscribe to messages that they are interested in, and programs can publish informative messages for clients to see. To make all this work, MQTT uses a broker process, which is a central server that keeps track of who wants to hear what and sends messages to clients accordingly.

To work out which clients are interested in which messages, each message has a topic, for example, /sensors/weather/temperature. A client can request to know just that temperature message or can use wildcards to subscribe to a collection of related messages. For example, /sensors/weather/# will listen to all messages that start with /sensors/weather/. The slash character is used much like the files and folders in a file system.

The two commands below give an introduction to how easy using MQTT can be. The two commands should be run in different terminal windows, with the mosquitto_sub executed first. When the mosquitto_pub command is run you should see abc appear on the terminal that is running mosquitto_sub. The -t option specifies the topic, and the -m option to mosquitto_pub gives the message to send.

$ mosquitto_sub -t /linux.com/test1
$ mosquitto_pub -t /linux.com/test1 -m abc

A relevant question here involves the timing of these commands. What if you run the mosquitto_pub command first? Nothing bad, but the mosquitto_sub command might not see the “abc” message at all. Now, if the topic was about the current temperature, and the topic was only published every hour, you might not want the client to have to wait that long to know the current temperature. You could have your weather station publish the temperature more frequently, for example, every 5 minutes or every 5 seconds. But, the trade-off is that you are sending messages very frequently for a value that changes infrequently in order for clients to have access to data right away.

To get around these timing issues, MQTT has the retain option. This is set when you publish a message using the -r option and tells the broker to keep that value and report it right away to any new clients that subscribe to messages on the topic. Using retain, you can run the publish command shown below first. Then, as soon as mosquitto_sub is executed, you should see def right away.

$ mosquitto_pub -t /linux.com/test2 -r -m def
$ mosquitto_sub -t /linux.com/+
def

In the preceding command, I’ve used the + in the topic used by the mosquitto_sub command. This lets you subscribe to all messages at that level. So, you will see /linux.com/test2 and /linux.com/test3 if it is sent, but not /linux.com/test2/something, because that is one level deeper in the hierarchy. The # ending will subscribe to an entire tree from a prefix regardless of how deep the topic gets. So, /linux.com/# would see /linux.com/test2/something and /linux.com/test2/a/b/c/d.

Another question to consider is what happens to messages that are sent when your program is not running. For example, a program might like to know if and how many times the refrigerator door has been opened in order to graph the efficiency of the refrigerator over time. The –disable-clean-session option to mosquitto_sub tells the broker that the program is interested in hearing those messages even if the program is not running at the moment.

Because the mosquitto_sub process might exit and might be running on a different computer to the broker, it needs to identify itself to the broker so that the broker knows who it is storing messages for and when that program has started up again. The –id option provides an identifier that is used to help the broker know who is the door watching client. Note that the open2, open3, and open4 messages might be sent when the mosquitto_sub is not running in the below example.

$ mosquitto_sub -t /linux.com/fridge/door --disable-clean-session --id doorwatcher -q 1
open1
^C
$ mosquitto_sub -t /linux.com/fridge/door --disable-clean-session --id doorwatcher -q 1
open2
open3
open4

$ mosquitto_pub -t /linux.com/fridge/door -m open1 -q 1
$ mosquitto_pub -t /linux.com/fridge/door -m open2 -q 1
$ mosquitto_pub -t /linux.com/fridge/door -m open3 -q 1
$ mosquitto_pub -t /linux.com/fridge/door -m open4 -q 1

The -q options that were used above tell MQTT what quality of service (QoS) we want for these messages. MQTT offers three levels of QoS. A QoS of 0 means that the message might be delivered, QoS of 1 makes sure the message is delivered, but that might happen more than once. A QoS of 2 means that the message will be delivered, and delivered only once. For messages to be stored for a client, the QoS must be 1 or more.

The message passing shown above is not limited to working without security. Both mosquitto_pub and mosquitto_sub sessions can use username and passwords or certificates to authenticate with the broker and TLS to protect communication. This can be a trade-off; as a protocol aimed at IoT, you might be more interested in knowing that a message is from a known good source and has not been altered than that the message has been encrypted. You might not care to keep it secret that the wind is at 20 miles an hour, but you do want to know that the message came from your weather station. So, you might want a valid message authentication code but the message itself can be sent in plain text format or using only a very rudimentary cipher.

ESP8266: Mixing in Small Microcontrollers over WiFi

The ESP8266 is a small, very inexpensive, microcontroller with WiFi support (Figure 1 above). Depending on the board, you might get it for under $10. Recent versions of the Arduino environment can be set up to compile and upload code to the ESP8266 board, making it easy to get up and running.

I used an ESP-201 board and had to set the following pin connections to run the microcontroller. Pin io0 is set to ground before applying power to the ESP8266 when you want to flash a new firmware to the board. Otherwise, leave io0 not connected and the ESP8266 will boot into your firmware right away. Note that the ESP8266 is a 3.3 volt machine and connecting it to 5 volts will likely damage the hardware.

ESP-201       Connection
------------------------
3.3v          3.3v regulated voltage
io0           pulled low to flash, not connected for normal operation
io5           pulled low                                      
chip_en       pulled high (to 3.3v of supply to esp)             
rx            tx on UART board
tx            rx on UART board
gnd           gnd  

The code for the ESP8266 shown below is based on an example from the Adafruit MQTT Library ESP8266. You will need to replace the WiFi SSID and PASSWORD with your local settings and update the MQTT_SERVER to the IP address of the local Linux machine on which you are running your MQTT server. You should be able to upload the program from a recent version of the Arduino IDE using a USB to TTL serial converter.

/***************************************************
 Adafruit MQTT Library ESP8266 Example

 Must use ESP8266 Arduino from:
   https://github.com/esp8266/Arduino

 Works great with Adafruit's Huzzah ESP board & Feather
 ----> https://www.adafruit.com/product/2471
 ----> https://www.adafruit.com/products/2821

 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 Written by Tony DiCola for Adafruit Industries.
 MIT license, all text above must be included in any redistribution
****************************************************/
#include
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

// the on off button feed turns this LED on/off
#define LED 14  

/************************* WiFi Access Point *********************************/

#define WLAN_SSID       "...FIXME..."
#define WLAN_PASS       "...FIXME..."

// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;

const char MQTT_SERVER[] PROGMEM    = "192.168.0.FIXME";
const char MQTT_USERNAME[] PROGMEM  = "ben";
const char MQTT_PASSWORD[] PROGMEM  = "secret";

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_USERNAME, MQTT_PASSWORD);

const char ONOFF_FEED[] PROGMEM = "/sensor/espled";
Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, ONOFF_FEED);

void setup() {
...
 WiFi.begin(WLAN_SSID, WLAN_PASS);
 while (WiFi.status() != WL_CONNECTED) {
   delay(500);
   Serial.print(".");
 }
 Serial.println();

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

 // Setup MQTT subscription for onoff & slider feed.
 mqtt.subscribe(&onoffbutton);
}

The main loop in this example reconnects to the MQTT broker if the connection was lost or has not yet been made. The readSubscription() call checks for any incoming data for subscriptions from MQTT and acts on the only subscription that the program has, turning an LED on and off depending on the message. The full example from which this code was taken is available on GitHub.

void loop() {
 MQTT_connect();

 Adafruit_MQTT_Subscribe *subscription;
 while ((subscription = mqtt.readSubscription(5000))) {
   // Check if its the onoff button feed
   if (subscription == &onoffbutton) {
     Serial.print(F("On-Off button: "));
     Serial.println((char *)onoffbutton.lastread);
     
     if (strcmp((char *)onoffbutton.lastread, "ON") == 0) {
       digitalWrite(LED, HIGH);
     }
     if (strcmp((char *)onoffbutton.lastread, "OFF") == 0) {
       digitalWrite(LED, LOW);
     }
   }

 }

 if(! mqtt.ping()) {
   mqtt.disconnect();
 }
}

Regardless of which MQTT implementation(s) you choose to run, by selecting an open standard, you are not limited in how your IoT devices can interact.