How to MQTT: Connect WIFI based microcontrollers

MQTT usage

MQTT is a protocol that is simple to use and is great for delivering messages between multiple devices. A MQTT system has a MQTT-broker (a server) and clients (publishers and subscribers). The broker has topics that publishers can add data to. When new data is added all the subscribers to the topic receives the new data. This is a decoupled system where no entity know each other. They only know the broker. This makes it easy to scale a small prototype to a big system.

In this guide we will use:

Adafruit.io (as MQTT-broker)
Arduino IDE
2xESP32 (as microcontrollers)

Setup

You need to follow this guide (http://fablab.ruc.dk/esp32-setup-guide/) to setup Arduino IDE to program ESP32.
Afterwards install the Adafruit MQTT Library. You click into the library manager like this:

Sketch -> Include Libraries -> Manage Libraries

Then search for "Adafruit MQTT" and pick the "Adafruit MQTT Library". Press install. Now the IDE should be ready, but also make sure you got Arduinos own Wifi library. But we still need a MQTT broker.

MQTT-broker (Adafruit IO)

We will use Adafruit IO (https://io.adafruit.com/) as our MQTT-broker. You need to sign up on the page. This is free but comes with a limit of sending no more than 30 data packages in a minute.

When you sign in you find the menu in the left side of the screen. Click on feeds. You will get a screen looking like this:

You now need to create the feeds you want. If its your first time i just recommend you to follow my example and add these three feeds:

pingFeed
dev1.publishFeed
dev2.publishFeed

You create the pingFeed by clicking:

Actions -> Create new feed

Then add the name of the feed and click the create button.

The pingFeed is used to ping between the device and the server. This makes sure the connection is up and running. Before we added this feed, the device sometimes stopped communicating with the server.

When adding dev1.publishFeed and dev2.publishFeed you need to create a group for each feed. This is not necessary but can be useful if you want a lot of different feeds. Name the groups dev1 and dev2, and give each group a feed called publishFeed.

The dev1 and dev2 publishFeed is made so both devices can publish data on their own. When those are done your feeds should look like this:

Now your MQTT-broker is ready and we will program the ESP32 to talk together with the broker.

Program the Device

The end point of this guide is the code snippet below. You can either copypaste it and try to understand it yourself, or read the step-by-step explanation that follows:

//************************************LIBRARIES****************************************************
#include <WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include <Arduino.h>

//************************************CONSTANTS****************************************************

//id of your device and the other device
#define id "1" //the number depends on wether the device is number 1 or 2, remember to change it for the other device
#define idOther "2" //remember to change this as well

// WiFi parameters
#define WLAN_SSID       "Fyrens Fede Iphone"
#define WLAN_PASS       "vfh65rmw"

// Adafruit IO
#define AIO_SERVER      "io.adafruit.com"
#define AIO_SERVERPORT  1883
#define AIO_USERNAME    "testUserVFH"
#define AIO_KEY         "c1c58bc9512d4b92bde63539ab68861b"  // Obtained from account info on io.adafruit.com
#define SERIAL_NUMBER   5555

//************************************GLOBAL VARIABLES****************************************************

// Functions


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

//Timer for ping. Ping is needed to hold the device connected to the server
unsigned long timer = 0;

// Store the MQTT server, client ID, username, and password in flash memory.
// This is required for using the Adafruit MQTT library.
const char MQTT_SERVER[]    = AIO_SERVER;  
const char MQTT_CLIENTID[]  = AIO_KEY __DATE__ __TIME__;  
const char MQTT_USERNAME[]  = AIO_USERNAME;  
const char MQTT_PASSWORD[]  = AIO_KEY;

// 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_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD);

/****************************** Feeds ***************************************/

// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>

//Publishes

Adafruit_MQTT_Publish devPing = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/pingfeed");  
Adafruit_MQTT_Publish devPublish = Adafruit_MQTT_Publish(&mqtt,  AIO_USERNAME "/feeds/dev" id ".publishfeed");

//Subscribes
Adafruit_MQTT_Subscribe devSubscribe = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/dev" idOther ".publishfeed");


void setup() {  
  //Initialize serial and wifi communication

  Serial.begin(115200);
  delay(10);
  Serial.print(F("Connecting to "));
  Serial.println(WLAN_SSID);

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

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

  //Subscribe to other device
  mqtt.subscribe(&devSubscribe);

  // connect to adafruit io
  Serial.print(F("Connecting to Adafruit IO... "));

  int8_t ret;

  while ((ret = mqtt.connect()) != 0) {

    yield();
    switch (ret) {
      case 1: Serial.println(F("Wrong protocol")); break;
      case 2: Serial.println(F("ID rejected")); break;
      case 3: Serial.println(F("Server unavail")); break;
      case 4: Serial.println(F("Bad user/pass")); break;
      case 5: Serial.println(F("Not authed")); break;
      case 6: Serial.println(F("Failed to subscribe")); break;
      default: Serial.println(F("Connection failed")); break;
    }

    if (ret >= 0)
      mqtt.disconnect();

    Serial.println(F("Retrying connection..."));
    delay(200);

  }

  Serial.println(F("Adafruit IO Connected!"));
}

void loop() {  
   // Subscribe to Adafruit MQTT-broker
  Adafruit_MQTT_Subscribe *subscription;

    // publishes to MQTT-broker for keeping itself alive.
  if (millis() - timer > 60000) // do something every 60 seconds)
  {
    timer = millis();
    devPing.publish(SERIAL_NUMBER);
  }


  if (millis()- timer > 10000 && millis()-timer < 10100)
  {
    int number = 100;
    devPublish.publish(number);
    Serial.println("pub number: " + String(number));
  }

  if (millis()- timer > 20000 && millis()-timer < 20100)
  {
    float decimal = 2.03;
    devPublish.publish(decimal);
    Serial.println("pub decimal: " + String(decimal));
  }

  if (millis()- timer > 30000 && millis()-timer < 30100)
  {
    String text = "hejsadawdsasdasdasdasdwfdawdasd";
    int charArrayLength = text.length()+1;
    char textInChars[charArrayLength];
    text.toCharArray(textInChars, charArrayLength);
    devPublish.publish(textInChars);
    Serial.println("pub text: " + text);
  }

  if (millis()- timer > 40000 && millis()-timer < 40100)
  {
    boolean boo = true;
    devPublish.publish(boo);
    Serial.println("pub boolean: " + String(boo));
  }

  if (subscription = mqtt.readSubscription(10)) {
    Serial.println("In the while");
    yield();
   if (subscription == &devSubscribe) {

      // convert mqtt ascii payload to String
      String stringValue((char *)devSubscribe.lastread);
      int intValue = stringValue.toInt();
      double doubleValue = stringValue.toDouble();

      Serial.print(F("Received: "));

      Serial.println("int: " + String(intValue));
      Serial.println("double: " + String(doubleValue));
      Serial.println("String: " + stringValue);
     }
   }
 }

Step by step code explanation

Libraries

To follow this tutorial you first need to include the used libraries:

#include <WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include <Arduino.h>

Constants

The code is running with the following constants:

//id of your device and the other device
#define id "2" 
#define idOther "1" 

// WiFi parameters
#define WLAN_SSID       "WIFINAME"
#define WLAN_PASS       "PASSWORD"

// Adafruit IO
#define AIO_SERVER      "io.adafruit.com"
#define AIO_SERVERPORT  1883
#define AIO_USERNAME    "YOURADAFRUITUSERNAME
#define AIO_KEY         "YOU AIOKEY"  // Obtained from account info on io.adafruit.com
#define SERIAL_NUMBER   5555

id and idOther is constants that is used to differentiate between the two devices. So it´s important that one of your devices is number 1 and references the other as number 2, and vice verca.

WLAN_ SSID is the name of your wifi and WLAN_PASS is the password for your wifi.

AIO_ SERVER and AIO_ SERVERPORT is constants that is refering to io.adafruit.com. They should not be changed
AIO_ USERNAME is your username on your adafruit account.
AIO_ KEY is a key that can be obtained from your adafruit page. You get this by clicking on the menu "View AIO Key". Then copypaste the active key and paste it into the string AIO_ KEY.

SERIAL_ NUMBER is actually not very important. Its just a constant we use to send something on the pingFeed. You could send whatever you want.

More constants and variables

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

//Timer for ping. Ping is needed to hold the device connected to the server
unsigned long timer = 0;

// Store the MQTT server, client ID, username, and password in flash memory.
// This is required for using the Adafruit MQTT library.
const char MQTT_SERVER[]    = AIO_SERVER;  
const char MQTT_CLIENTID[]  = AIO_KEY __DATE__ __TIME__;  
const char MQTT_USERNAME[]  = AIO_USERNAME;  
const char MQTT_PASSWORD[]  = AIO_KEY;

// 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_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD);  

The client is a wifi object from Arduinos wifi library. This is sooner used for connecting to wifi.

The timer is used for timing our events.

The MQTT_ SERVER[], MQTT_ CLIENTID[], MQTT_ USERNAME[], MQTT
_ PASSWORD[] is needed to store the server in flashmemory. This has something to do with the Adafruit MQTT library. We pass these constants to Adafruit_MQTT _ Client.

Publish and Subscribe

Now we can finally make the publish and subscribe objects:

//Publishes

Adafruit_MQTT_Publish devPing = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/pingfeed");  
Adafruit_MQTT_Publish devPublish = Adafruit_MQTT_Publish(&mqtt,  AIO_USERNAME "/feeds/dev" id ".publishfeed");

//Subscribes
Adafruit_MQTT_Subscribe devSubscribe = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/dev" idOther ".publishfeed");

These are objects from the Adafruit library and they need to refer to the path of the feeds you want to use. In our case the first device publishes to YOURADAFRUITUSERNAME/feeds/dev1.publishfeed and subscribes to YOURADAFRUITUSERNAME/feeds/dev2.publishfeed. Because of this its important to check if the id and idOther has the right values. Else you will have to devices that publishes, but none to receive the data.

Setup()

The setup is doing three things:

Serial

The device starts a Serial connection at a baudrate 115200

Serial.begin(115200);  

This is only used when the device is connected to a computer and for example makes it possible to make Serial communication and debugging.

Wifi

Then we start a connection to the wifi. The while loop is running until the connection is established. So if the devices doesn´t work then try to open your Serial Monitor and check if the connection is established.

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

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

When the connection is established "WiFi connected" gets printed and the program will continue.

Server connection

When the ESP32 has a connection to WiFi the next step is to find the server:

 //Subscribe to other device
  mqtt.subscribe(&devSubscribe);

Serial.print(F("Connecting to Adafruit IO... "));

  int8_t ret;

  while ((ret = mqtt.connect()) != 0) {

    yield();
    switch (ret) {
      case 1: Serial.println(F("Wrong protocol")); break;
      case 2: Serial.println(F("ID rejected")); break;
      case 3: Serial.println(F("Server unavail")); break;
      case 4: Serial.println(F("Bad user/pass")); break;
      case 5: Serial.println(F("Not authed")); break;
      case 6: Serial.println(F("Failed to subscribe")); break;
      default: Serial.println(F("Connection failed")); break;
    }

    if (ret >= 0)
      mqtt.disconnect();

    Serial.println(F("Retrying connection..."));
    delay(200);

  }

  Serial.println(F("Adafruit IO Connected!"));

This makes the program try to connect to the server and gives you a error message if the connection fails. If you have written everything correct until this point, there should be no errors.
Now your program is ready for sending and receiving

Loop()

This program starts with subscribing to the MQTT-broker. This is easy with the library and is this line of code:

// Subscribe to Adafruit MQTT-broker
  Adafruit_MQTT_Subscribe *subscription;

In this example we will send some different datapackages in a timed schedule, but feel free to experiment in this part of the program.

The ping is every 60000 milliseconds (every minute). NOTE: THIS ONE IS IMPORTANT FOR THE SYSTEM NOT TO CRASH

Ping

// publishes to MQTT-broker for keeping itself alive.
  if (millis() - timer > 60000) // do something every 60 seconds)
  {
    timer = millis();
    devPing.publish(SERIAL_NUMBER);
  }

Publish numbers

 if (millis()- timer > 10000 && millis()-timer < 10100)
  {
    int number = 100;
    devPublish.publish(number);
    Serial.println("pub number: " + String(number));
  }

Publish charArray

The library doesn't allow strings to be passed. Therefore we need to convert the string to a char array, when we want to publish a text with adafruit MQTT.

if (millis()- timer > 30000 && millis()-timer < 30100)  
  {
    String text = "Hello World";
    int charArrayLength = text.length()+1;
    char textInChars[charArrayLength];
    text.toCharArray(textInChars, charArrayLength);
    devPublish.publish(textInChars);
    Serial.println("pub text: " + text);
  }

Publish booleans

This is publishing a boolean, but the subscriber will receive the value as 0 or 1. This is actually the same as true and false, and is not a problem.

if (millis()- timer > 40000 && millis()-timer < 40100)  
  {
    boolean boo = true;
    devPublish.publish(boo);
    Serial.println("pub boolean: " + String(boo));
  }

Subscribtion

The subscribtion happens when the other devices publishes something.
This code converts the mqtt ascii payload message into a String. The String can easily be converted into ints or other datatypes you want.

if (subscription = mqtt.readSubscription(10)) {  
    Serial.println("In the while");
    yield();
   if (subscription == &devSubscribe) {

      // convert mqtt ascii payload to String
      String stringValue((char *)devSubscribe.lastread);
      int intValue = stringValue.toInt();
      double doubleValue = stringValue.toDouble();

      Serial.print(F("Received: "));

      Serial.println("int: " + String(intValue));
      Serial.println("double: " + String(doubleValue));
      Serial.println("String: " + stringValue);
     }
   }

When you have implemented all the code on one device, try to add another device. You need to switch the ID in the beginning of the code for the devices to talk to each other.