Build an Air Quality Monitor with Live Readings and Alerts

7 min read Michael Carroll on Dec 1, 2022
Try PubNub Today

Free up to 1MM monthly messages. No credit card required.

Subscribe to our newsletter

By submitting this form, you are agreeing to our Terms and Conditions and Privacy Policy.

Build your own system to monitor air quality (carbon monoxide levels), temperature, and humidity, and publish readings to a live dashboard with alerts with PubNub all in real time.

The World Health Organization estimates that 4.2 million people die each year from causes directly attributed to air pollution. With that in mind, even being aware of air particulates and toxins wage a difficult battle could help reduce the 7 million premature deaths every year from exposure to air pollution. Many people often don’t even realize that their own home’s indoor air quality is polluted.

However, in this day and age of powerful IoT technologies, any person can combat bad air quality. There are devices specifically made to detect particulate matter (PM) using PM sensors and CO2 sensors, send the information to a central hub using Bluetooth or WiFi, and display this data to users through devices such as home assistants like Amazon Alexa. Air purifiers are also used to help combat poor air quality by filtering the polluted air.

If you want to create your own DIY air quality sensor and monitoring system, sensors and microcontrollers have gotten so cheap, small, and simple to implement, that practically anyone can install any type of monitor or sensor in their very own home. Among other possibilities, these sensors can trigger things like alerts and notifications to spur user action.

In this tutorial, you'll learn how easy it is to create an air quality sensor and monitoring system that obtains air quality data using a few low-cost electronics and PubNub Functions. The sensor will extract air quality information with multiple sensors, graph that data in real time, and even set off a speaker alarm if the air quality reaches a harmful critical point, effectively creating your own DIY air quality monitor.

carbon monoxoide sensor diy

Required Hardware

Listed below are the required materials needed to complete this tutorial. You can purchase this hardware from the links provided below or from online retailers such as Amazon and AliExpress.

Arduino Uno

Arduino Uno is one of the cheapest open-source microcontrollers available today. Since the Uno has an onboard analog-to-digital converter adapter, the analog signals produced from the sensors used in this tutorial can be properly read.


A breadboard is a tool used to prototype and test circuits. Breadboards have two long rails (red rail for power and blue rail for ground) on each of the sides that are each connected vertically down the board. This is where engineers typically plug in a power supply and ground so that the whole rail can be easily used as the power and ground connector. At the center of the board, there are groups of 5-holed horizontal lines. Each of these lines is horizontally connected (5-hole connection) which is used for the circuit itself. You can even connect one grouping to another to make a 10-hole connection and so on and so forth.

You can purchase a breadboard with some wires typically in a bundled kit.

Basic Speaker

A speaker is essentially a piece of vibrating metal that moves up by a magnet when fed power and falls down when unpowered. When the magnet is turned on and off at a specific frequency, a tone can be played. Purchase a cheap speaker from an online retailor mentioned before.


Each of the sensors you're going to use throughout this tutorial operates on the same fundamental principle. A specific type of material is placed inside a sensor and an electric current is fed through it. When certain environmental conditions occur that react with the sensor’s specific material, the material’s resistance to the current increases or decreases. The current then will be affected and the sensor can translate that change into proper sensor units.

You'll need to pick up the following sensors needed for this tutorial:


The wiring of each of these sensors follows the same pattern:

  1. Connect the ground pin of the sensor to the ground of the Arduino.

  2. Connect the power pin of the sensor to the 5.5V pin of the Arduino.

  3. Connect the data pin of the sensor to a GPIO pin on the Arduino.

The sensors were connected in the following schematic.

air quality monitor schematic

Designing the Logic

The code for this logic is set up in three parts: Arduino sketch, publishing code, and graphing HTML code.

  1. The Arduino sketch will handle the direct communication between the sensors and the Arduino.

  2. The publishing code will relay the data received by the Arduino to the PubNub Arduino SDK for publishing, which is used to communicate with PubNub’s APIs.

  3. The HTML code will then visualize the data into a graph in real time on an HTML webpage.

Note: Please note that the PubNub Arduino SDK is no longer officially supported.

Before you begin implementing the code behind, make sure you sign up for a free PubNub account, as you'll need your publish/subscribe keys to send information across the PubNub Network.

Arduino Sketch

For the Arduino code, you will be developing in the Arduino IDE in order to utilize its serial bus capabilities and simple method call structure.

Begin by importing all of the necessary libraries for each of our sensors so you can communicate with them in the Arduino IDE.

To do this, download the .zip file from each library’s GitHub and add them to your Sketch.

GitHub links: DHT11MQ135MQ7, and MQ2.

#include <dht.h>
#include <MQ135.h>
#include <MQ7.h>
#include <MQ2.h>

Then you will need to create variables and instances for each of your sensors.

#define DHT11_PIN 16	 //place this line below the include statements
int chk;
dht DHT;
float M135;
MQ135 gasSensor = MQ135(A3);
int pin = A0;
MQ2 mq2(pin);
float ppm;
MQ7 mq7(A1, 5.0);

Next, you will need to create a setup function to initialize your Arduino’s baud rate as well as initiate any sensor’s necessary setup modules (in this case the MQ2).

void setup()

To keep the program continuously running, create a loop method with a delay of one second.

void loop()
  //rest of the code

Inside the loop is where you will implement the code to communicate with the sensor’s data. For each sensor, you use the methods defined in their libraries to extract the data from the sensor and format the raw voltage reading to a recognizable unit of measure.

The code for each sensor is as follows:

MQ7 = analogRead(A1);
ppm = mq7.getPPM();
//Serial.print(" MQ7 = ");
M135 = gasSensor.getPPM();
//Serial.print(" MQ135 = ");
float lpg = mq2.readLPG();
float smoke = mq2.readSmoke();
//Serial.print(" lpg = ");
//Serial.print(" Smoke = ");
chk = DHT.read11(DHT11_PIN);
if(DHT.temperature > -500)  //don’t read any absurd readings
  //Serial.print("Temperature = ");
 if(DHT.humidity > -500)	   //don’t read any absurd readings
  //Serial.print("Humidity = ");

Note: You need to comment out the serial.print lines except for temperature and humidity. This makes future steps simpler. But if you’d like to test your devices, uncomment them. You can view the serial prints in the Arduino serial monitor. If you do this, make sure each sensor is publishing to a new line each time.

Next, you'll connect the speaker to the program. As discussed before, you need to implement the speaker to operate at a certain frequency to play a specific tone.

Create a new file named pitches.h and include the pitches.h file via #include "pitches.h".

#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978

Next, copy and paste these methods that are responsible for playing a sound using the tone referenced in pitches.h.

int melody[] = {NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C4};
// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {4, 8, 8, 4, 4, 4, 4, 4};
void playTones() 
// iterate over the notes of the melody:
for (int thisNote = 0; thisNote < 8; thisNote++) 
  // to calculate the note duration, take one second divided by the note type.
  //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
  int noteDuration = 1000 / noteDurations[thisNote];
  tone(8, melody[thisNote], noteDuration);

Now you can have the sound trigger if any of the sensor values reach above a certain threshold. You will need to create your own critical variables through trial and error as each sensor will come with a noticeable margin of error.

if((lg > lg_crit) || (smoke > smoke_crit) || (ppm > ppm_crit) || (DHT.temperature > temp_crit) || (DHT.humidity > hum_crit) || (M135 > M135_crit))

Once you have finished, select your Arduino’s USB port, hit the green checkmark to compile, and the green right arrow to upload it to your connected Arduino.

Tip: You can find the name of the port your Arduino is connected to by going into the top menu: Tools->Port.

Publishing Code

You might be wondering, what is the need to separate the code just to publish messages from the Arduino? Isn't it easier to do it straight from the sketch?

To simplify a complicated answer, the PubNub Arduino SDK requires the use of a WiFi shield to enable Internet capabilities. Since that is out of the scope of this project, we will revert to an alternative method: pySerial.

pySerial is a library that tunes in on the Arduino’s serial monitor. Anything printed over the serial monitor can be read by pySerial. Thus, we can extract the data from the serial bus and publish that data through PubNub.

To install pySerial, type the following command into your terminal.

pip install pyserial

Then create a python script by giving it a name and include the necessary PubNub libraries to be able to use the PubNub Arduino SDK.

import serial
from pubnub.callbacks import SubscribeCallback
from pubnub.enums import PNStatusCategory
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub import PubNub
from time import sleep
pnconfig = PNConfiguration()

Then you can set up your pySerial port connection.

//Then set up your Pyserial port connection with the following line:
Arduino_Serial = serial.Serial('YOUR PORT NAME',9600, timeout=1)
//Now create a PubNub Instance
pnconfig.subscribe_key = "YOUR SUBSCRIBE KEY"
pnconfig.publish_key = "YOUR PUBLISH KEY"  
pnconfig.ssl = False
pubnub = PubNub(pnconfig)
//Create a Publishing Callback
def publish_callback(envelope, status):

Next, create the infinite loop of the program that continuously extracts and publishes data. Be careful, as pySerial’s serial bus monitor can only see data coming in serially. It cannot distinguish names or variables, only numbers separated by the new line character.

Since you are going to work with two different sensor readings (temperature and humidity), we can expect the first line of data to be from sensor A, the second by Sensor B, the third by Sensor A, the fourth from Sensor B, and so on.

Going back to the Arduino sketch covered earlier, the temperature sensor is printing first in the code, so you can expect that the pattern will go temperature, then humidity, then repeat. You should capture the data in that pattern.

#read in data from the serial bus 5(depends on the length of target data)
#characters at a time
#NOTE: you must decode the data as serial code is different from human code!
temp = str(Arduino_Serial.readline().decode('ascii'))
hum = str(Arduino_Serial.readline().decode('ascii'))

Next, print the values to the terminal before publishing using PubNub in order to see if you’re getting valid data.

print(temp+ '
print(hum + '

If everything is running smoothly, your terminal should look as follows. Please note that the alternation of data – 29 corresponds to temperature and 34 corresponds to humidity).

iot carbon monoxide monitor terminal 1

Note: Please note that the rest of the tutorial discusses publishing this data to PubNub's real-time data visualization framework, PubNub EON. PubNub EON and the PubNub EON Chart SDK which is used to communicate with Project EON, are no longer officially supported.

Finally, you're going to publish this data to our real-time data visualization framework: PubNub EON. In order to send the data that EON can parse and graph, you need to format the data first into JSON format.

dictionary = {"eon": {"temp": temp, "hum": hum }}

Then you can publish to the same channel that the chart is subscribed.


HTML Graphical Representation Code

You will now use PubNub EON to display the readings in real time on a live chart.

To add PubNub EON to any HTML page, add a

tag in your body, which will inject the actual chart. Then import the script tag SDK:

<script src=""></script>
<link rel="stylesheet" href=""/>

Then subscribe to the eon-chart channel where you will be posting data.

       channels: ['eon-chart'],
       history: true,
       flow: true,
       pubnub: pubnub,
       generate: {
         bindto: '#chart',
         data: {
           labels: false

You now have a connected device collecting and streaming air quality readings and publishing these results to a live dashboard, all in real time. You'll be alerted with a sound to indicate that these thresholds you set up earlier have been reached.

If you would like to learn more about how to power your IoT applications and smart home systems, take a look at our IoT resources.

If you have any other questions or concerns, please feel free to reach out to

More from PubNub

NPP and HIPAA: Notice of Privacy Practices Definition
Healthcare CategoryJan 6, 20235 min read

NPP and HIPAA: Notice of Privacy Practices Definition

A Notice of Privacy Practices (NPP) is one of the requirements of HIPAA and helps patients understand their personal data rights.

Michael Carroll

Michael Carroll

HIPAA Violation Examples
Healthcare CategoryJan 5, 20236 min read

HIPAA Violation Examples

HIPAA violations can be financially expensive and devastating to a brand. Examine some examples of HIPAA violations, and learn...

Michael Carroll

Michael Carroll

HIPAA Technical Safeguards: How To Protect Sensitive Data
Healthcare CategoryJan 5, 20236 min read

HIPAA Technical Safeguards: How To Protect Sensitive Data

HIPAA covered entities must follow the five technical safeguards to achieve HIPAA compliance and prevent data corruption.

Michael Carroll

Michael Carroll