In this article, we will show how to use ESP-WROOM-32 to get real-time data from air quality sensor and display it on ThingsBoard dashboard
Things used in this project
Hardware components
Story
Introduction
In this article, we will show how to use ESP-WROOM-32 to get real-time data from the air quality sensor and display it on the ThingsBoard dashboard. To send data to Thingsboard we will use ThingsBoard Arduino SDK.
Preparation
Arduino libraries:
-WiFi
-MQUnifiedsensor
-ThingsBoard Arduino SDK and dependencies:
-MQTT PubSub Client — for interacting with MQTT.
-ArduinoJSON — for dealing with JSON files.
-Arduino Http Client — for interacting with ThingsBoard using HTTP
Step 1
In the first step, we have to connect all required hardware.
The connection scheme is:
Step 2
In order to start programming, we have to download all required libraries. To do this click on Tools > Manage Libraries... and in the Search input alternately install the following libraries:
2.2 Connecting ESP-WROOM-32 to WiFi
Once the WiFi library is installed on the Arduino IDE, you can now add the header in code.
#include <WiFi.h>
Define SSID and password values for your WiFi point:
const char* ssid = "YOUR_NETWORK_SSID";
const char* password = "YOUR_PASSWORD";
Optionally, with setup() function we start a connection to WiFi and start up Serial port for logging. Serial Monitor will give us some debug information and sensor data.
void setup()
{
Serial.begin(9600);
if(Serial) Serial.println("Serial is open");
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected, IP address: ");
Serial.println(WiFi.localIP());
}
2.3 Collecting data from Air Quality sensor
To get data from the air quality sensor we use MQUnifiedsensor library. It is suitable for various MQ sensors.
So, we have to include the header on our sketch (below the WiFi library).
#include <MQUnifiedsensor.h>
According to the official documentation of the library, we should define setting variables in the global scope:
Please review the documentation if using another MQ sensors to get define keys.
#define placa "ESP-32"
#define Voltage_Resolution 3.3
#define pin 34
#define type "MQ-135"
#define ADC_Bit_Resolution 12
#define RatioMQ135CleanAir 3.6
double CO2 = (0);
MQUnifiedsensor MQ135(placa, Voltage_Resolution, ADC_Bit_Resolution, pin, type);
Now proceed with setup() function section:
void setup()
{
...
//Set math model to calculate the PPM concentration and the value of constants
MQ135.setRegressionMethod(1); //_PPM = a*ratio^b
MQ135.setA(110.47);
MQ135.setB(-2.862);
// Configurate the ecuation values to get NH4 concentration
MQ135.init();
Serial.print("Calibrating please wait.");
float calcR0 = 0;
for(int i = 1; i<=10; i ++) {
MQ135.update(); // Update data, the arduino will be read the voltage on the analog pin
calcR0 += MQ135.calibrate(RatioMQ135CleanAir);
Serial.print(".");
}
MQ135.setR0(calcR0/10);
Serial.println(" done!.");
if(isinf(calcR0)) { Serial.println("Warning: Conection issue founded, R0 is infite (Open circuit detected) please check your wiring and supply"); while(1);}
if(calcR0 == 0){Serial.println("Warning: Conection issue founded, R0 is zero (Analog pin with short circuit to ground) please check your wiring and supply"); while(1);}
/***************************** MQ CAlibration **************************/
MQ135.serialDebug(false);
}
CO2 value calculation requires setA and setB functions added. Set 110.47 coefficient for A value and -2.862 coefficient for B value.
NB: Coefficients are specific to the sensor model, meaning that MQ-135 sensor's index mismatches MQ-4 sensor's, for instance.
To accomplish setup of CO2 monitoring within loop() function we must map the key:
void loop() {
MQ135.update(); // Update data, the arduino will be read the voltage on the analog pin
CO2 = MQ135.readSensor(); // Sensor will read CO2 concentration using the model and a and b values setted before or in the setup
Serial.print("CO2: ");
Serial.println(CO2);
delay(5000);
}
Step 3
To send data to the ThingsBoard, we will use Arduino SDK that has all high-level functions for this.
3. Sending data into ThingsBoard Platform
To do this we have to define device token and ThingsBoard host:
#define TOKEN "YOUR_TOKEN_TO_DEVICE"
#define THINGSBOARD_SERVER "thingsboard.cloud"
// Initialize ThingsBoard client
WiFiClient espClient;
// Initialize ThingsBoard instance
ThingsBoard tb(espClient);
// the Wifi radio's status
int status = WL_IDLE_STATUS;
3.1 Obtaining the token.
If you have ThingsBoard up and running and corresponding to use case device entity exists:
-Go to Device Groups of the device owner, select All.
-Click on the device row in the table to open device details
-Click "Copy access token". The Token will be copied to your clipboard.
1. If using a PaaS subscription, Log in or Sign Up to the thingsboard.cloud and perform previous steps.
2. If you do not have any device entity so far, Create a new device:
a. At the left navigation bar click on Device groups > All
b. At the right top menu click on the + button
c. Fill in all inputs in the form
d. Click Add button
3. In the device list click on the created device
4. Click on Copy access token
5. Paste token to the global variable TOKEN
To send our decimal value from the sensor to ThingsBoard we should use sendTelemetryFloat() function in the main loop:
void loop() {
...
if (!tb.connected()) {
// Connect to the ThingsBoard
Serial.print("Connecting to: ");
Serial.print(THINGSBOARD_SERVER);
Serial.print(" with token ");
Serial.println(TOKEN);
if (!tb.connect(THINGSBOARD_SERVER, TOKEN)) {
Serial.println("Failed to connect");
return;
}
}
...
}
The code above checks whether the client is connected to the ThingsBoard instance, and if not the client will try to reconnect to it.
Code Listing
You can find it in the attached files.
Result
Was this article useful? Place a comment below!
Schematics
Schema
Code
Arduino Code
C/C++
#include <WiFi.h>
#include <MQUnifiedsensor.h>
#include <ThingsBoard.h>
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
#define TOKEN "YOUR_DEVICE_TOKEN"
#define THINGSBOARD_SERVER "thingsboard.cloud"
// Initialize ThingsBoard client
WiFiClient espClient;
// Initialize ThingsBoard instance
ThingsBoard tb(espClient);
// the Wifi radio's status
int status = WL_IDLE_STATUS;
//Definitions
#define placa "ESP-32"
#define Voltage_Resolution 3.3
#define pin 34 //Analog input 0 of your arduino
#define type "MQ-135" //MQ135
#define ADC_Bit_Resolution 12 // For arduino UNO/MEGA/NANO
#define RatioMQ135CleanAir 3.6//RS / R0 = 3.6 ppm
double CO2 = (0);
MQUnifiedsensor MQ135(placa, Voltage_Resolution, ADC_Bit_Resolution, pin, type);
void setup()
{
Serial.begin(9600);
if(Serial) Serial.println("Serial is open");
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected, IP address: ");
Serial.println(WiFi.localIP());
//Set math model to calculate the PPM concentration and the value of constants
MQ135.setRegressionMethod(1); //_PPM = a*ratio^b
MQ135.init();
Serial.print("Calibrating please wait.");
float calcR0 = 0;
for(int i = 1; i<=10; i ++)
{
MQ135.update(); // Update data, the arduino will be read the voltage on the analog pin
calcR0 += MQ135.calibrate(RatioMQ135CleanAir);
Serial.print(".");
}
MQ135.setR0(calcR0/10);
Serial.println(" done!.");
if(isinf(calcR0)) {Serial.println("Warning: Conection issue founded, R0 is infite (Open circuit detected) please check your wiring and supply"); while(1);}
if(calcR0 == 0){Serial.println("Warning: Conection issue founded, R0 is zero (Analog pin with short circuit to ground) please check your wiring and supply"); while(1);}
/***************************** MQ CAlibration ********************************************/
MQ135.serialDebug(false);
}
void loop()
{
if (!tb.connected()) {
// Connect to the ThingsBoard
Serial.print("Connecting to: ");
Serial.print(THINGSBOARD_SERVER);
Serial.print(" with token ");
Serial.println(TOKEN);
if (!tb.connect(THINGSBOARD_SERVER, TOKEN)) {
Serial.println("Failed to connect");
return;
}
}
MQ135.update(); // Update data, the arduino will be read the voltage on the analog pin
MQ135.setA(110.47); MQ135.setB(-2.862);
CO2 = MQ135.readSensor();
MQ135.setA(605.18); MQ135.setB(-3.937);
float CO = MQ135.readSensor();
MQ135.setA(77.255); MQ135.setB(-3.18);
float Alcohol = MQ135.readSensor();
MQ135.setA(44.947); MQ135.setB(-3.445);
float Toluene = MQ135.readSensor();
MQ135.setA(102.2 ); MQ135.setB(-2.473);
float NH4 = MQ135.readSensor();
MQ135.setA(34.668); MQ135.setB(-3.369);
float Acetone = MQ135.readSensor();
Serial.print("CO2: ");
Serial.println(CO2);
Serial.print("Light level: ");
Serial.println(lux);
Serial.println("Sending data...");
tb.sendTelemetryFloat("CO2", CO2);
tb.sendTelemetryFloat("CO", CO);
tb.sendTelemetryFloat("Alcohol", Alcohol);
tb.sendTelemetryFloat("Toluene", Toluene);
tb.sendTelemetryFloat("NH4", NH4);
tb.sendTelemetryFloat("Acetone", Acetone);
tb.loop();
delay(1000); //Sampling frequency
}
Dashboard JSON File
JSON
{
"title": "Air Quality",
"image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": {
"description": "",
"widgets": {
"11a80b8e-e962-de17-dc1c-cc1543aebf2c": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 8,
"sizeY": 5,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "4bf9b60c-91d0-61e1-ec10-d1bdca48100d",
"filterId": null,
"dataKeys": [
{
"name": "CO2",
"type": "timeseries",
"label": "CO2",
"color": "#2196f3",
"settings": {
"excludeFromStacking": false,
"hideDataByDefault": false,
"disableDataHiding": false,
"removeFromLegend": false,
"showLines": true,
"fillLines": false,
"showPoints": false,
"showPointShape": "circle",
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
"showPointsLineWidth": 5,
"showPointsRadius": 3,
"tooltipValueFormatter": "",
"showSeparateAxis": false,
"axisTitle": "",
"axisPosition": "left",
"axisTicksFormatter": "",
"thresholds": [
{
"thresholdValueSource": "predefinedValue"
}
],
"comparisonSettings": {
"showValuesForComparison": true,
"comparisonValuesLabel": "",
"color": ""
}
},
"_hash": 0.019369294834221784
},
{
"name": "Alcohol",
"type": "timeseries",
"label": "Alcohol",
"color": "#f44336",
"settings": {
"excludeFromStacking": false,
"hideDataByDefault": false,
"disableDataHiding": false,
"removeFromLegend": false,
"showLines": true,
"fillLines": false,
"showPoints": false,
"showPointShape": "circle",
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
"showPointsLineWidth": 5,
"showPointsRadius": 3,
"tooltipValueFormatter": "",
"showSeparateAxis": false,
"axisTitle": "",
"axisPosition": "left",
"axisTicksFormatter": "",
"thresholds": [
{
"thresholdValueSource": "predefinedValue"
}
],
"comparisonSettings": {
"showValuesForComparison": true,
"comparisonValuesLabel": "",
"color": ""
}
},
"_hash": 0.10773096272975002
},
{
"name": "CO",
"type": "timeseries",
"label": "CO",
"color": "#ffc107",
"settings": {
"excludeFromStacking": false,
"hideDataByDefault": false,
"disableDataHiding": false,
"removeFromLegend": false,
"showLines": true,
"fillLines": false,
"showPoints": false,
"showPointShape": "circle",
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
"showPointsLineWidth": 5,
"showPointsRadius": 3,
"tooltipValueFormatter": "",
"showSeparateAxis": false,
"axisTitle": "",
"axisPosition": "left",
"axisTicksFormatter": "",
"thresholds": [
{
"thresholdValueSource": "predefinedValue"
}
],
"comparisonSettings": {
"showValuesForComparison": true,
"comparisonValuesLabel": "",
"color": ""
}
},
"_hash": 0.16983810931760046
},
{
"name": "NH4",
"type": "timeseries",
"label": "NH4",
"color": "#607d8b",
"settings": {
"excludeFromStacking": false,
"hideDataByDefault": false,
"disableDataHiding": false,
"removeFromLegend": false,
"showLines": true,
"fillLines": false,
"showPoints": false,
"showPointShape": "circle",
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
"showPointsLineWidth": 5,
"showPointsRadius": 3,
"tooltipValueFormatter": "",
"showSeparateAxis": false,
"axisTitle": "",
"axisPosition": "left",
"axisTicksFormatter": "",
"thresholds": [
{
"thresholdValueSource": "predefinedValue"
}
],
"comparisonSettings": {
"showValuesForComparison": true,
"comparisonValuesLabel": "",
"color": ""
}
},
"_hash": 0.8795072817187648
},
{
"name": "Acetone",
"type": "timeseries",
"label": "Acetone",
"color": "#607d8b",
"settings": {
"excludeFromStacking": false,
"hideDataByDefault": false,
"disableDataHiding": false,
"removeFromLegend": false,
"showLines": true,
"fillLines": false,
"showPoints": false,
"showPointShape": "circle",
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
"showPointsLineWidth": 5,
"showPointsRadius": 3,
"tooltipValueFormatter": "",
"showSeparateAxis": false,
"axisTitle": "",
"axisPosition": "left",
"axisTicksFormatter": "",
"thresholds": [
{
"thresholdValueSource": "predefinedValue"
}
],
"comparisonSettings": {
"showValuesForComparison": true,
"comparisonValuesLabel": "",
"color": ""
}
},
"_hash": 0.06822020169930765
},
{
"name": "Toluene",
"type": "timeseries",
"label": "Toluene",
"color": "#9c27b0",
"settings": {
"excludeFromStacking": false,
"hideDataByDefault": false,
"disableDataHiding": false,
"removeFromLegend": false,
"showLines": true,
"fillLines": false,
"showPoints": false,
"showPointShape": "circle",
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
"showPointsLineWidth": 5,
"showPointsRadius": 3,
"tooltipValueFormatter": "",
"showSeparateAxis": false,
"axisTitle": "",
"axisPosition": "left",
"axisTicksFormatter": "",
"thresholds": [
{
"thresholdValueSource": "predefinedValue"
}
],
"comparisonSettings": {
"showValuesForComparison": true,
"comparisonValuesLabel": "",
"color": ""
}
},
"_hash": 0.8240666057673744
}
]
}
],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
},
"showTitle": true,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "8px",
"settings": {
"shadowSize": 4,
"fontColor": "#545454",
"fontSize": 10,
"xaxis": {
"showLabels": true,
"color": "#545454"
},
"yaxis": {
"showLabels": true,
"color": "#545454",
"tickDecimals": 2
},
"grid": {
"color": "#545454",
"tickColor": "#DDDDDD",
"verticalLines": true,
"horizontalLines": true,
"outlineWidth": 1
},
"stack": false,
"tooltipIndividual": false,
"showTooltip": true,
"timeForComparison": "previousInterval",
"xaxisSecond": {
"axisPosition": "top",
"showLabels": true
},
"comparisonEnabled": false,
"smoothLines": true,
"tooltipCumulative": false
},
"title": "CO2",
"dropShadow": true,
"enableFullscreen": true,
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"useDashboardTimewindow": true,
"displayTimewindow": true,
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
"titleTooltip": "",
"enableDataExport": true,
"widgetStyle": {},
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "bottom",
"sortDataKeys": false,
"showMin": false,
"showMax": false,
"showAvg": true,
"showTotal": false
}
},
"row": 0,
"col": 0,
"id": "11a80b8e-e962-de17-dc1c-cc1543aebf2c"
}
},
"states": {
"default": {
"name": "Air Quality",
"root": true,
"layouts": {
"main": {
"widgets": {
"11a80b8e-e962-de17-dc1c-cc1543aebf2c": {
"sizeX": 12,
"sizeY": 6,
"row": 0,
"col": 0
},
"12d74d94-f73a-83d4-b708-6b4a4d7ad5f0": {
"sizeX": 6,
"sizeY": 6,
"row": 0,
"col": 12
},
"8b62ff45-cf96-5002-555a-a610115b545e": {
"sizeX": 12,
"sizeY": 5,
"row": 6,
"col": 0
},
"cd052af7-efa9-2e58-8197-0b406f3723e7": {
"sizeX": 12,
"sizeY": 3,
"row": 6,
"col": 12
},
"32311d7a-cfd7-23ea-a3f1-7687a79e7cb5": {
"sizeX": 12,
"sizeY": 2,
"row": 9,
"col": 12
},
"a03a3244-2944-55f8-35fe-df348de691ed": {
"sizeX": 6,
"sizeY": 6,
"row": 0,
"col": 18
}
},
"gridSettings": {
"backgroundColor": "#eeeeee",
"columns": 24,
"margin": 10,
"backgroundSizeMode": "100%"
}
}
}
}
},
"entityAliases": {
"4bf9b60c-91d0-61e1-ec10-d1bdca48100d": {
"id": "4bf9b60c-91d0-61e1-ec10-d1bdca48100d",
"alias": "Alias",
"filter": {
"type": "singleEntity",
"resolveMultiple": false,
"singleEntity": {
"entityType": "DEVICE",
"id": "d62db230-25b5-11ec-a9e6-556e8dbef35c"
}
}
}
},
"filters": {},
"timewindow": {
"hideInterval": false,
"hideAggregation": false,
"hideAggInterval": false,
"hideTimezone": false,
"selectedTab": 0,
"realtime": {
"realtimeType": 0,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY",
"interval": 1000
},
"aggregation": {
"type": "NONE",
"limit": 25000
}
},
"settings": {
"stateControllerId": "entity",
"showTitle": false,
"showDashboardsSelect": true,
"showEntitiesSelect": true,
"showDashboardTimewindow": true,
"showDashboardExport": true,
"toolbarAlwaysOpen": true
}
},
"name": "Air Quality"
}
The article was first published in hackster, October 22, 2021
cr: https://www.hackster.io/thingsboard/part-1-how-to-connect-esp-wroom-32-mq135-and-thingsboard-4a4bb4
author: Team ThingsBoard: Vitalik Bidochka, Andrew Shvayka, Ilya Barkov