Build an Air Quality Sensor with a MQ135 Sensor to detect smoke and other harmful gas for Alexa
In this section, we will walk you through creating an air quality sensor using ESP32 or ESP8266. You will then be able to view the air quality via Amazon Alexa and receive a push notification via the Sinric Pro app when the air quality drops below a certain threshold level.
Prerequisites :
- ESP32, ESP8266 x 1.
- MQ135 x 1.
- Jumper Wires.

Quick introduction to MQ135
The MQ135 is a semiconductor gas sensor that is commonly used in air quality control equipment. It is sensitive to a wide range of gases, including ammonia, sulfide, benzene, and smoke. The sensor has two outputs: a digital output that goes high when the gas concentration exceeds a certain threshold, and an analog output that provides a voltage proportional to the gas concentration.
The MQ135 sensor requires a power supply of 5V and consumes about 150mA of current. The digital output can be connected to a ESP32, ESP8266 or Pico W to measure the gas concentration.
How to calibrate the MQ135 sensor
The MQ135 does not output ppm directly. Its resistance (Rs) changes with the gas concentration, and the ppm value is calculated from the ratio between Rs and R0, the sensor’s resistance in clean air. R0 is different for every sensor, so each one has to be calibrated.
1. Burn-in (once): Put your sensor outside where there is enough fresh air (ideally at 20°C & 33% humidity according to the datasheet) and leave it powered on for 12-24 hours to burn it in and stabilize.
2. Warm-up (every power on): the sensor has a heater. Readings are too high for the first few minutes after power on, so wait until they settle before using them.
3. Measure R0 in clean air: with the sensor still in fresh air, let the TroykaMQ library used in this tutorial calculate R0 and print it:
#include <TroykaMQ.h>
#define PIN_MQ135 34 // ESP32. Use A0 on ESP8266
MQ135 mq135(PIN_MQ135);
void setup() {
Serial.begin(115200);
mq135.calibrate(); // run this in clean air
Serial.print("Ro = ");
Serial.println(mq135.getRo());
}
4. Reuse the value: write down the printed R0 and pass it to calibrate(). The sensor then does not need fresh air every time it restarts:
mq135.calibrate(YOUR_RO_VALUE);
Temperature and humidity change the reading, so calibrate in conditions close to where the sensor will be used. The MQ135 is good for detecting that air quality has changed, but it is not a precision instrument.
MQ135 reading stuck high (around 3800 to 4095)?
On an ESP32, analogRead() returns 0 to 4095. A reading that stays at or near the top of that range usually has one of these causes:
- The output voltage is above the ADC range. The module is powered from 5V, so its A0 output can go above the 3.3 V limit of the ESP32 ADC and the reading saturates. Add a voltage divider between A0 and the GPIO.
- The sensor is still warming up, or a new sensor has not been burned in yet.
- An ADC2 pin is used while Wi-Fi is on. Use an ADC1 pin such as GPIO 34.
- The sensor was calibrated in polluted air, so R0 is wrong. Calibrate again in fresh air.
Specification sheet: SNS-MQ135
Wiring

| MCU | A0 Pin | VCC Pin |
|---|---|---|
| ESP32 | 34 (Analog ADC1_CH6) | 5V |
| ESP8266 | A0 | 5V/VIN |
| Pico W | GP26 (ADC0) | 5V |
Note: on ESP32, ADC2 (GPIO04, GPIO02, GPIO15) is unstable when Wi-Fi is being used.
Let’s verify that motion sensor is wired correctly and working.
#include <TroykaMQ.h> // https://github.com/amperka/TroykaMQ
#if defined(ESP8266)
#define SENSOR_PIN A0
#elif defined(ESP32)
#define SENSOR_PIN 34
#endif
MQ135 mq135(SENSOR_PIN);
void setup() {
Serial.begin(9600);
// before calibrating the sensor, warm it up for 60 seconds calibrate the sensor in clean air
mq135.calibrate();
// if you know the resistance of the sensor in clean air
// you can specify it manually, eg: 160 mq135.calibrate(160);
Serial.print("Ro = ");
Serial.println(mq135.getRo());
}
void loop() {
// display gas values in ppm
Serial.print(mq135.readCO2());
Serial.println("ppm");
delay(1000);
}
Arduino IDE Serial Monitor will show the motion detections like this:

Detecting poor Air Quality
#include <TroykaMQ.h> // https://github.com/amperka/TroykaMQ
#if defined(ESP8266)
#define SENSOR_PIN A0
#elif defined(ESP32)
#define SENSOR_PIN 34
#endif
MQ135 mq135(SENSOR_PIN);
const int AQI_THRESHOLD_SEVERE = 700;
const int AQI_THRESHOLD_VERY_POOR = 600;
const int AQI_THRESHOLD_POOR = 500;
const int AQI_THRESHOLD_MODERATE = 400;
int ppm = 0;
void setup() {
Serial.begin(9600);
// before calibrating the sensor, warm it up for 60 seconds calibrate the sensor in clean air
mq135.calibrate();
// if you know the resistance of the sensor in clean air
// you can specify it manually, eg: 160 mq135.calibrate(160);
Serial.print("Ro = ");
Serial.println(mq135.getRo());
}
void loop() {
ppm = mq135.readCO2();
if (ppm > AQI_THRESHOLD_SEVERE) {
Serial.print("Air quality is SEVERE!");
} else if (ppm > AQI_THRESHOLD_VERY_POOR) {
Serial.print("Air quality is VERY POOR!");
} else if (ppm > AQI_THRESHOLD_POOR) {
Serial.print("Air quality is VERY POOR!");
} else if (ppm > AQI_THRESHOLD_MODERATE) {
Serial.print("Air quality is MODERATE");
} else {
Serial.print("Air quality is GOOD");
}
delay(1000);
}
Step 1 : Connect to Sinric Pro
Step 1.1 : Creating a custom device type for Air Quality Sensor.
Sinric Pro does not have a built-in device type for Air Quality Sensor with PPM so we are going to create a custom device type for Water Sensor using Device Template feature to achieve.
-
PPM Value
-
Air Quality Is Severe, Very Poor, Poor, Moderate or Good.
Note: You can use the device template import feature mentioned below to skip creating the full template.
-
Login to your Sinric Pro account and goto Device Templates
-
Click Add Device Template. Enter name and description as Air Quality Sensor and select Air Quality Sensor as device type

- Click Capabilities.
Here we must select the features of our Air Quality Sensor. We want to know whether Air Quality is Severe, Very Poor, Poor, Moderate or Good. So let’s drag a Range, a Mode and Push Notification capability.

-
What’s Range capability?
Range capability can be use to represents a number. eg: current speed of a blender.
-
What’s Mode capability?
Mode capability can be use to represents a value out of list of predefined values. eg: current wash cycle mode of a washing machine.
-
What’s Push Notification capability?
Notification capability provides the ability to send a push notification message from the code.
Click on Configure button and setup the two capabilities like below.

Click on Save to save.

Click on Save to save the template.
Now you can see the template we just created.
Import an existing template?
If you are feeling lazy setup all the Modes and Range values, you can use the import feature.

Paste this Template:
{
"name": "Air Quality Sensor",
"description": "Air Quality Sensor",
"deviceTypeId": "5ff0b112994fd31b7d5e8065",
"capabilities": [
{
"id": "5ff0b41b994fd31b7d5e8961",
"mode": {
"instanceId": "modeInstance1",
"locale": "en-US",
"modeName": "Air Quality",
"nonControllable": true,
"modeValues": [
"SEVERE",
"VERY POOR",
"POOR",
"MODERATE",
"GOOD"
]
}
},
{
"id": "5ff0b45f994fd31b7d5e89c2",
"range": {
"instanceId": "rangeInstance1",
"locale": "en-US",
"rangeName": "ppm",
"minimumValue": 1,
"maximumValue": 1024,
"precision": 1,
"unitOfMeasure": "600e7986a7a09c3f3230f8fb",
"presets": [],
"nonControllable": true
}
},
{
"id": "62a707bd9e2f39090569e2d3"
}
]
}
-
Login to your Sinric Pro account, go to Devices menu on your left and click Add Device button (On top left).
-
Enter the device name Air Quality Sensor, description Air Quality Sensor and select the device type as Air Quality Sensor from Your Device Templates.


-
Click Others tab and Click Save
-
Next screen will show the credentials required to connect the device you just created.

-
Copy the Device Id, App Key and App Secret Keep these values secure. DO NOT SHARE THEM ON PUBLIC FORUMS !
-
Click on Code Generator to Generate the code. Click on Download to download the code.

Step 2 : Connect to Sinric Pro
Step 2.1 Install Sinric Pro Library

Step 2.2 Install TroykaMQ
TroykaMQ is not available in Arduino Library Manager. So you must download it from https://github.com/amperka/TroykaMQ and copy it over to C:\Users\<your username>\Documents\Arduino\libraries
2.2 Complete Code
AirQualitySensor.h
#ifndef _AIRQUALITYSENSOR_H_
#define _AIRQUALITYSENSOR_H_
#include <SinricProDevice.h>
#include <Capabilities/ModeController.h>
#include <Capabilities/RangeController.h>
#include <Capabilities/PushNotification.h>
class AirQualitySensor
: public SinricProDevice
, public ModeController<AirQualitySensor>
, public RangeController<AirQualitySensor>
, public PushNotification<AirQualitySensor> {
friend class ModeController<AirQualitySensor>;
friend class RangeController<AirQualitySensor>;
friend class PushNotification<AirQualitySensor>;
public:
AirQualitySensor(const String &deviceId) : SinricProDevice(deviceId, "AirQualitySensor") {};
};
#endif
sinricpro-air-quality-sensor.ino
// Uncomment the following line to enable serial debug output
//#define ENABLE_DEBUG
#ifdef ENABLE_DEBUG
#define DEBUG_ESP_PORT Serial
#define NODEBUG_WEBSOCKETS
#define NDEBUG
#endif
#include <Arduino.h>
#ifdef ESP8266
#include <ESP8266WiFi.h>
#endif
#ifdef ESP32
#include <WiFi.h>
#endif
#include <TroykaMQ.h> // https://github.com/amperka/TroykaMQ
#include <SinricPro.h>
#include "AirQualitySensor.h"
#define WIFI_SSID "" // Your WiFI SSID name
#define WIFI_PASS "" // Your WiFi Password.
#define APP_KEY "" // Should look like "de0bxxxx-1x3x-4x3x-ax2x-5dabxxxxxxxx" (Get it from Portal -> Secrets)
#define APP_SECRET "" // Should look like "5f36xxxx-x3x7-4x3x-xexe-e86724a9xxxx-4c4axxxx-3x3x-x5xe-x9x3-333d65xxxxxx" (Get it from Portal -> Secrets)
#define DEVICE_ID "" // Should look like "5dc1564130xxxxxxxxxxxxxx" (Get it from Portal -> Devices)
#define BAUD_RATE 115200 // Change baudrate to your need (used for serial monitor)
#define EVENT_WAIT_TIME 60000 // send event every 60 seconds
#define BAUD_RATE 115200
#define EVENT_WAIT_TIME 60000 // send event every 60 seconds
#if defined(ESP8266)
#define SENSOR_PIN A0
#elif defined(ESP32)
#define SENSOR_PIN 34
#endif
MQ135 mq135(SENSOR_PIN);
const int AQI_THRESHOLD_SEVERE = 700;
const int AQI_THRESHOLD_VERY_POOR = 600;
const int AQI_THRESHOLD_POOR = 500;
const int AQI_THRESHOLD_MODERATE = 400;
int ppm = 0;
int lastPPM = 0;
String lastAirQualityLevelStr = "";
AirQualitySensor &airQualitySensor = SinricPro[DEVICE_ID];
// ModeController
void updateMode(String mode) {
airQualitySensor.sendModeEvent("modeInstance1", mode, "PHYSICAL_INTERACTION");
}
// RangeController
void updateRangeValue(int value) {
airQualitySensor.sendRangeValueEvent("rangeInstance1", value);
}
// PushNotificationController
void sendPushNotification(String notification) {
airQualitySensor.sendPushNotification(notification);
}
/*********
* Setup *
*********/
void setupAirQualitySensor() {
// before calibrating the sensor, warm it up for 60 seconds calibrate the sensor in clean air
mq135.calibrate();
// if you know the resistance of the sensor in clean air
// you can specify it manually, eg: 160 mq135.calibrate(160);
Serial.print("Ro = ");
Serial.println(mq135.getRo());
}
void setupSinricPro() {
SinricPro.onConnected([]{ Serial.printf("[SinricPro]: Connected\r\n"); });
SinricPro.onDisconnected([]{ Serial.printf("[SinricPro]: Disconnected\r\n"); });
SinricPro.begin(APP_KEY, APP_SECRET);
};
void setupWiFi() {
#if defined(ESP8266)
WiFi.setSleepMode(WIFI_NONE_SLEEP);
WiFi.setAutoReconnect(true);
#elif defined(ESP32)
WiFi.setSleep(false);
WiFi.setAutoReconnect(true);
#endif
WiFi.begin(SSID, PASS);
Serial.printf("[WiFi]: Connecting to %s", SSID);
while (WiFi.status() != WL_CONNECTED) {
Serial.printf(".");
delay(250);
}
Serial.printf("connected\r\n");
}
void setup() {
Serial.begin(BAUD_RATE);
setupAirQualitySensor();
setupWiFi();
setupSinricPro();
}
/********
* Loop *
********/
void handleAirQualitySensor() {
if (SinricPro.isConnected() == false) {
return;
}
static unsigned long last_millis;
unsigned long current_millis = millis();
if (last_millis && current_millis - last_millis < EVENT_WAIT_TIME) return;
last_millis = current_millis;
ppm = mq135.readCO2();
Serial.printf("Present ppm: %d\r\n", ppm);
if (ppm == lastPPM) {
Serial.printf("PPM did not changed. do nothing...!\r\n");
return;
}
/* determine air quality level */
String airQualityLevelStr = "";
if (ppm > AQI_THRESHOLD_SEVERE) {
airQualityLevelStr = "SEVERE";
} else if (ppm > AQI_THRESHOLD_VERY_POOR) {
airQualityLevelStr = "VERY POOR";
} else if (ppm > AQI_THRESHOLD_POOR) {
airQualityLevelStr = "POOR";
} else if (ppm > AQI_THRESHOLD_MODERATE) {
airQualityLevelStr = "MODERATE";
} else {
airQualityLevelStr = "GOOD";
}
/* Update air quality level label if changed */
if(lastAirQualityLevelStr != airQualityLevelStr) {
lastAirQualityLevelStr = airQualityLevelStr;
updateMode(lastAirQualityLevelStr);
}
delay(500);
/* update PPM label */
updateRangeValue(ppm);
/* send a push notification when air quality level is bad! */
if (ppm > AQI_THRESHOLD_SEVERE) {
sendPushNotification("Air quality is SEVERE!");
} else if (ppm > AQI_THRESHOLD_VERY_POOR) {
sendPushNotification("Air quality is VERY POOR!");
} else if (ppm > AQI_THRESHOLD_POOR) {
sendPushNotification("Air quality is VERY POOR!");
}
lastPPM = ppm; // save to compare next time.
}
void loop() {
handleAirQualitySensor();
SinricPro.handle();
}
Now you should be able to see the air quality status and water level via Alexa, Sinric Pro App
Alexa, What’s the air quality (mode name) in air quality sensor(device name)
Alexa, What’s the ppm (range name) in air quality sensor(device name)


Step 3 : Voice Announcements!
✅ How it works:
1️⃣ Add a Switch in SinricPro (your secret trigger!)
2️⃣ Create an Alexa Routine that speaks your message when a “Air quality is bad!” when Switch turned on
3️⃣ Send a power state event to turn ON that switch from your code
void triggerVoiceAnnouncement() {
SinricProSwitch& mySwitch = SinricPro[SWITCH_ID];
mySwitch.sendPowerStateEvent(true);
}
Troubleshooting
-
Google Home or SmartThings are not Supported.
-
Please refer to our Troubleshooting page for more details.
Related tutorials
- How to control an ESP32 or ESP8266 with Alexa and Google Home
- MQ-3 alcohol and gas sensor
- Capacitive soil moisture sensor (HW-390)
- Water leak, flood and rain sensor
This document is open source. See a typo? Please create an issue