Ubidots has developed libraries for some of the most popular development boards on the market but with makers designing new proprietary hardware daily, we want to provide the basic steps to developing a script yourself to send data properly using MQTT. Remember that MQTT is a lightweight protocol very popular for IoT devices and it is highly recommended for control purposes (click here for more details on MQTT).

First, it is recommended that you read about our MQTT API and understand the basics. Below, are some standard definitions needed to integrate your devices with our application enablement platform:

  • Client: A client is the device which sends data to Ubidots for updating or creating variables. Clients are also able to retrieve data.
  • Broker: Brokers are the counterparts of a client. Brokers processes all the data and have a main responsibility to receive messages, update variables, and notify the clients about changes in the platform and any hold sessions. (The url of the Ubidots broker is things.ubidots.com with communication port 1883.)
  • Publish: Publish is similar to POST at HTTP, when a client publishes a message, the broker will either update or create a new device depending on the command code.
  • Subscribe: Similar to the GET function in HTTP, subscribe is the method to obtain values with a huge difference in the GET request. The difference being you do not have to be continuously ask the server for each value in your custom script. For example, if a variable's value changes, Ubidots will automatically update you (the user) of any changes. Thus saving data requests and processing time for your device and overall project functionality and expenses.

Now that you know more about the basics of MQTT, let's begin to setup your Arduino IDE. We need to install the PubSubClient library, this is one of the most popular MQTT libraries in the Arduino environment. If you want to know more about the library, refer to this its github repository and to its API documentation site .

To install the library, follow these steps:

  1. First, open the Arduino IDE and go to the Arduino IDE library manager SKETCH > INCLUDE LIBRARIES > MANAGE LIBRARIES

2. Search and install the "PubSubClient" library:
Note: be sure to install the latest version of this library.

CODING:

Once you have installed the library, let's begin to code the script for sending data properly. For this example we will use a NodeMCU to send data and will use this script for sending data.

Note: For this explanation, we have truncated the code into 9 separate sections to help you understand the whole code. The full code will appear in your IDE as a single entry once complete and can be found here

/****************************************
 * Include Libraries
 ****************************************/

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

/****************************************
 * Define Constants
 ****************************************/

#define WIFISSID "...." // Put your WifiSSID here
#define PASSWORD "...." // Put your wifi password here
#define TOKEN "..." // Put your Ubidots' TOKEN
#define VARIABLE_LABEL "...." // Assing the variable label
#define DEVICE_LABEL "...." // Assing the device label
#define MQTT_CLIENT_NAME "....." // MQTT client Name

We begin by including the necessary libraries: The PubSubClient will operate in the background allowing for a MQTT connection. The ESP8266 wrappers for the Arduino IDE and the standard C language library <stdio.h>. The lines below *Define Constants" within the above code are for determining the correct path for data to transmit with Ubidots. Please update your WiFi name, password, input your user token (read here to get your custom token), and labels of your device and variable that will be created in your ubidots account. 

The final constant needed is the MQTT_CLIENT_NAME, this is special because it is the ID with which your device will be identified by the broker so it MUST be unique. If your device tries to connect with the same ID that has already been taken by another device, the connection will be refused. Please create your own all alphanumeric 8-12+ character MQTT_CLIENT_NAME and input into the code accordingly.

Hint: Need some help creating a unique MQTT_CLIENT_NAME, check out this random ascii builder, or simply use the MAC address of your device as no MAC address is the same as another.

char mqttBroker[] = "things.ubidots.com";
char payload[700];
char topic[150];

// Space to store values to send
char str_box_temp[6];
char str_lat[6];
char str_lng[6];

Now we need to declare additional char arrays. In the above code you will find the mqttBroker variable stores the Ubidots' broker, the payload reserves memory space for data to be sent later in the routine.
The remaining features of this code allow for additional memory storage and prompt data transmission. 

/****************************************
 * Initialize constructors for objects
 ****************************************/

ESP8266WiFiMulti WiFiMulti;
WiFiClient ubidots;
PubSubClient client(ubidots);


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

Now we need to initialize the necessary constructors, the first one (ESP8266WiFIMulti WiFiMulti) will be used exclusively for making the connection to the wifi access point. Then we will initialize a wificlient (ubidots) that will be pass as the parameter to the PubSubClient constructor.

Next, we have to establish a callback function. This function is very important because it handles the changes of your variables in Ubidots and is exclusive of the PubSubClient library. I'll explain the arguments of this function below:

  • char* topic: The topic is the endpoint of your variable , according to our api it should be /v1.6/devices/{LABEL_DEVICE} or /v1.6/devices/{LABEL_DEVICE}/{LABEL_VARIABLE}/lv  if you are subscribed to get only values from your variables.
  • byte* payload: Is the response obtained directly from the broker once a change in one of your subscribed variables has taken place.
  • unsigned int length: The length of the payload.

So having the arguments clarified, let's see what the function will do. Once your variable has changed, it prints through the serial port which topic has changed and concludes with printing the response from the broker.
Keep in mind that this function is called every time that a change has taken place in your subscribed variables. So if you need to implement additional tasks into you code, for example, controlling a relay, you will need to edit this function. Please reference this video for additional support in how to edit this function. 

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.println("Attempting MQTT connection...");
   
    // Attempt to connect
    if (client.connect(MQTT_CLIENT_NAME, TOKEN,"")) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 2 seconds");
      // Wait 2 seconds before retrying
      delay(2000);
    }
  }
}

Next, we have coded an additional function for reconnecting in case the MQTT goes down. In the first loop above, you will see an argument for determining a connection status with the connected() method from the PubSubClient object. If the device is not connected, then the code will try to initialize a connection to the broker with the connect(char* clientName, char* user, char* password) method. Here you can find that we insert as arguments the client ID previously defined and your TOKEN as username. You can insert your token as password too but on the Ubidots side we will only process your username so it does not matter which you put as your password.

/****************************************
 * Main Functions
 ****************************************/
 
void setup() {
    Serial.begin(115200);
    pinMode(A0, INPUT);
    WiFiMulti.addAP(WIFISSID, PASSWORD);
    Serial.println();
    Serial.println();
    Serial.print("Wait for WiFi... ");

    while(WiFiMulti.run() != WL_CONNECTED) {
        Serial.print(".");
        delay(500);
    }
   
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    client.setServer(mqttBroker, 1883);
    client.setCallback(callback);

From here the script should be familiar for as it now only contains setup and loop functions. As a setup() function, we initialize the serial port at 115k bauds per second and set the analog pin as input of the NodeMCU to read the sensor values. We then connect to the wifi access point and print some debug messages. The first loop of the code is for avoiding any routine from starting if the device is not connected to the access point. Once the device is confirmed as connected, we print a debug message with the local IP assigned to the device.

Now, here comes two interesting functions, setServer(),a method of PubSubClient to set the broker url and the port to begin communications. And, setCallBack() which makes available the callback() function defined previously. 

Please note that the previous two methods (setServe and setCallBack) are exclusive of the client object defined by the PubSubClient constructor.

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

       // Subscribes for getting the value of the control variable in the temperature-box device

      char* topicToSubscribe;
      sprintf(topicToSubscribe, "%s", ""); // Cleans the context of the char
      sprintf(topicToSubscribe, "%s%s", "/v1.6/devices/", "temperature-box");
      sprintf(topicToSubscribe, "%s/%s/lv", topicToSubscribe, "control");
      client.subscribe(topicToSubscribe);
    }

Now Iet us begin with the  loop() function. Firstly, verify that the device is connected; if not, call the previous reconnect() auxiliary function. Once connected, we subscribe to a topic in Ubidots for retrieving data from a variable named 'control' in a device named 'temperature-box.' You can find all of this information in the device tab of your Ubidots application. (Keep in mind that the variable and the device should have been created previously in your Ubidots application). To retrieve the value of that variable, we need to subscribe to a topic with the below structure according to our API:

/v1.6/devices/{LABEL_DEVICE}/{LABEL_VARIABLE}/lv 

To build the above structure, we use the C function sprintf(). Then we call the subscribe() method for subscribing to your topic in Ubidots. Remember that any change on that variable will be handled by the callback()  function defined previously.

    float temperature = analogRead(A0);
    float lat = 6.101;
    float lng= -1.293;
   
    /* 4 is mininum width, 2 is precision; float value is copied onto str_temp*/
    dtostrf(temperature, 4, 2, str_temp);
    dtostrf(lat, 4, 2, str_lat);
    dtostrf(lng, 4, 2, str_lng);

Now can begin to read the value from our sensor and store it locally on the variable called temperature. (In this example, the latitude and longitude values are simulated since our NodeMCU doesn't have GPS.) The function dtostrf() simply lets us store a float temperature value as a char array for sending the value to the broker later. 

Remember that we need to send a plane text format string chain. We are moving from a float type to a string type variable.

sprintf(topic, "%s", ""); // Cleans the topic content
sprintf(topic, "%s%s", "/v1.6/devices/", DEVICE_LABEL);

sprintf(payload, "%s", ""); //Cleans the payload
sprintf(payload, "{\"%s\":", VARIABLE_LABEL); // Adds the variable label   sprintf(payload, "%s {\"value\": %s", payload, str_temp); // Adds the value
sprintf(payload, "%s, \"context\":{\"lat\": %s, \"lng\": %s}", payload, str_lat, str_lng); // Adds coordinates
sprintf(payload, "%s } }", payload); // Closes the dictionary brackets

Our goal is to publish data so we begin to construct the topic where we will publish our values. Remember that with sprintf() function, the Device Label within the /v1.6/devices/{LABEL_DEVICE} structure will be replaced. 


Now on the payload char we will store the value to send. For this we need to build a JSON dictionary according to our MQTT API. In this tutorial we will be sending a temperature value with the position of the device. For this,  we have constructed the following JSON dictionary:

{"temperature":{"value":20, "context":{"lat": 6.21, "lng":-1.2}}} 

We built, step-by-step, the JSON dictionary using the sprintf() C function and stored it in the payload variable below.

client.publish(topic, payload);
    client.loop();
    delay(1000);
}

Finally, we call the publish method and insert as arguments the topic where we will publish and the payload that contains our values.

Remember that the full code can be downloaded and dropped into you IDE from here.

TESTING:

Now let's test the subscription. I've created a dashboard with an on/off control button associated with the variable 'control' in the device 'temperature-box.' In the callback() function, all changes to the variable are handled to trigger some action (in our case, the action was simply to print the topic and its value, but you could - for example, to turn on or off a GPIO). Here you can see how it works:

Next, we will check the publish status, I've set again 'temperature-box' as  DEVICE_LABEL and as 'temperature' as VARIABLE_LABEL with a delay of 1000 milliseconds for sending values. If the device or variable does not exist then Ubidots' core will autogenerate these for you. Here is the visual results for sending data every second:

GOAL REACHED:

With the completed code, our script works great! Now it is your turn to give it a try and adopt as your own according to your specific needs. Once you've mastered sending data to and from Ubidots, you can begin to port your script to a library file for the Arduino IDE following this guide that fulfills the Arduino standard.

Do not forget to leave us your comments in our community forums or you can connect with us vis social media at Facebook, Twitter or Hackster.

Did this answer your question?