I’m currently testing a non-dispersive infrared (NDIR) based sensor to detect carbon dioxide (CO2) parts per million (PPM) levels using an MH-Z19B sensor, coupled with an ESP8266 based NodeMCU V3.
Compared to other sensor technologies (such as filament heating of electrochemical plates used by the MQ-135 sensor), the MH-Z19B has the following advantages:
- is factory calibrated (does not need calibration)
- is accurate and does not fluctuate radically
- PPM values are already corrected for temperature dependancies
- readily works out of the box with no 24-48 hour “burn in” period
- is specific to CO2 PPM (filament heating of electrochemical plates are not specific to a particular gas, and respond to more than one gas)
The MH-Z19B is connected to the NodeMCU V3 as follows.
I have programmed the ESP8266 based NodeMCU V3 to connect to my WiFi network, and send the MH-Z19B sensor’s CO2 PPM measurements to a Thingspeak channel.
The graph below shows the previous hour of CO2 PPM measurements, updated every 15 seconds.
The gauge below shows the latest CO2 PPM reading from the MH-Z19B sensor.
The following C++ code reads the CO2 PPM (and sensor temperature) from the MH-Z19B sensor’s universal asynchronous receiver transmitter (UART) port (i.e. Rxd and Txd pins), and uploads them to my Thingspeak channel.
Note: there is no error in the C++ code below and the schematic diagram above. With serial connections, a transmit from one device must go to a receive on the other device, and the receive from one device must go to a transmit of the other device.
#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
SoftwareSerial co2Serial(D3, D4); // define MH-Z19 RX TX D3 (GPIO0) and D4 (GPIO2)
unsigned long startTime = millis();
const char* ssid = "Your WiFi SSID";
const char* password = "Your WiFi Password";
const char* host = "api.thingspeak.com";
String apiKey = "Your Thingspeak API write key of your Thingspeak channel"; // thingspeak.com api key goes here
WiFiClient client;
void setup() {
Serial.begin(9600);
co2Serial.begin(9600);
connectToWiFi();
}
void loop() {
Serial.println("------------------------------");
Serial.print("Time from start: ");
Serial.print((millis() - startTime) / 1000);
Serial.println(" s");
int ppm_uart = readCO2UART();
delay(15000);
}
int readCO2UART() {
byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
char response[9];
Serial.println("Sending CO2 request...");
co2Serial.write(cmd, 9); //request PPM CO2
// clear the buffer
memset(response, 0, 9);
int i = 0;
while (co2Serial.available() == 0) {
Serial.print("Waiting for response ");
Serial.print(i);
Serial.println(" s");
delay(1000);
i++;
}
if (co2Serial.available() > 0) {
co2Serial.readBytes(response, 9);
}
// print out the response in hexa
for (int i = 0; i < 9; i++) {
Serial.print(String(response[i], HEX));
Serial.print(" ");
}
Serial.println("");
// checksum
byte check = getCheckSum(response);
if (response[8] != check) {
Serial.println("Checksum not OK!");
Serial.print("Received: ");
Serial.println(response[8]);
Serial.print("Should be: ");
Serial.println(check);
}
// ppm
int ppm_uart = 256 * (int)response[2] + response[3];
Serial.print("UART CO2 PPM: ");
Serial.println(ppm_uart);
// temp
byte temp = response[4] - 40;
Serial.print("Sensor Temperature: ");
Serial.println(temp);
// status
byte status = response[5];
Serial.print("Status: ");
Serial.println(status);
if (status == 0x40) {
Serial.println("Status OK");
}
if (client.connect(host,80)) {
String postStr = apiKey;
postStr +="&field1=";
postStr += String(ppm_uart);
postStr +="&field2=";
postStr += String(temp);
postStr += "\r\n\r\n";
client.print("POST /update HTTP/1.1\n");
client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");
client.print("X-THINGSPEAKAPIKEY: "+apiKey+"\n");
client.print("Content-Type: application/x-www-form-urlencoded\n");
client.print("Content-Length: ");
client.print(postStr.length());
client.print("\n\n");
client.print(postStr);
}
client.stop();
Serial.print("Uploaded to Thingspeak ");
return ppm_uart;
}
byte getCheckSum(char *packet) {
byte i;
unsigned char checksum = 0;
for (i = 1; i < 8; i++) {
checksum += packet[i];
}
checksum = 0xff - checksum;
checksum += 1;
return checksum;
}
void connectToWiFi(){
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to ");
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
Note: in the C++ code above, my WiFi credentials and Thingspeak channel’s API write key have been removed.
Future updates to this C++ code can be found at https://github.com/robertwisbey/esp8266_mh-z19b_thingspeak.
In addition to embedding Thingspeak data on web pages like above, or directly viewing the Thingspeak channel data on the Thingspeak website, you can also view Thingspeak channels in a smartphone app called Thingview (iOS link or Android link).
The great thing about Thingspeak is that you can set up a “React” App and “ThingHTTP” App, and couple them with the IFTTT (IF This Then That) platform, so it sends you notifications when the CO2 PPM level exceeds a configured threshold.
Note: in the screenshot above, I intentionally set a low threshold value of 400 PPM in order to get notifications sent to my iPhone. Normally you would set a higher threshold value, such as 1000 PPM.
More information on how to configure Thingspeak with IFTTT will follow soon !