Have you ever struggled to make it through an afternoon meeting? Can't stop yawning? Sleeping through your 3 hour lectures? No, I'm not selling you an energy supplement. You probably didn't get enough sleep last night, most of us don't. But, your drowsiness might not be completely your fault! Poor ventilation and high levels of carbon dioxide could be causing you to feel sleepy!
Using this NDIR CO2 sensor you can accurately measure the CO2 levels and read the values using a simple UART serial interface. Conventional CO2 sensors used to draw a lot of power and took time to warm up the lamp before they were primed to take a reading. Now using an LED and infrared detector you can accurately measure gasses using this 3.3v sensor which pulls less than 1.5ma on average. It uses optical dispersion and some other witchcraft, check out Cozir's datasheet for some more details, it's impressive.
Right so what do I need to start measuring the 'sleepiness factor' of my office cubicle?
- Arduino Due or Zero
- AnduinoWiFi shield
- Cozir CO2 sensor
- A few jumpers to connect and power up the sensor.
Connections for this one are simple, the sensor has 10 pins but you'll only need to wire up 4.
Using the Due connect 3.3v to 3V3, GND to Ground, Rx(DIO19) to Tx, and Tx(DIO18) to Rx. Be sure you've "crossed" the UART wires and remember you can't use Tx(DIO1) and Rx(DIO0) unless you'd like to forgo using the serial term to monitor your readings. I've used Serial1 for the sensor although you could use any of the three remaining UARTs.
** If you'd like to bypass the Arduino for now and test sending commands directly to the sensor just open up putty or your favorite serial term and connect at 9600 baud, 8bits, no parity, 1 stop bit. You may need to enable sending '/r/n' on each submission of ascii.**
***This sensor is 3.3v TTL so be sure to use a logic shifter if coms originate on a 5v source***
Calibrating the sensorThere are a few ways to calibrate the CO2 sensor. One of the best ways is saturating the sensor in a known gas (Nitrogen) which contains no Carbon Dioxide. This will generate a known zero reading. If you don't have any Nitrogen laying around you can also fairly accurately calibrate using fresh air. So grab your sunglasses, we're going on a field trip.
When you're outside you're going to want to run the example sketch below and uncomment
calibrateFreshAir();
in the Setup() routine. This sends the 'G' command over serial to the sensor requesting...
calibration! Now this isn't perfect, since I don't know exactly what the current CO2 concentration conditions really are on the 14th floor here in NYC (possibly a bit higher here than "Earth's" average in Hawaii). But since our thresholds for actually physically sensing differences in the environment are in the 1,000's of ppm I think we're pretty safe to use this 450ppm fresh air reading as our calibration point for measuring our conference rooms and classrooms.
char buffer[20] = {0};
int c = 0;
void setup() {
Serial.begin(9600);
while(!Serial){};
Serial1.begin(9600);
while(!Serial){};
Serial.println("Begin reading CO2 Levels");
//setOperatingMode(CZR_STREAMING);
//setOperatingMode(CZR_POLLING);
//calibrateFreshAir();
}
void loop() {
delay(10000);
c = Request("Z");
Serial.print("CO2 : ");Serial.println(c);
Serial.println("");
}
int Request(char* s)
{
buffer[0] = '\0';
int idx = 0;
Command(s);
delay(250);
while(Serial1.available())
{
buffer[idx++] = Serial1.read();
}
buffer[idx] = '\0';
uint16_t rv = 0;
switch(buffer[1])
{
case 'T' :
rv = atoi(&buffer[5]);
if (buffer[4] == 1) rv += 1000;
break;
default :
rv = atoi(&buffer[2]);
break;
}
return rv;
}
void Command(char* s)
{
Serial1.print(s);
Serial1.print("\r\n");
}
uint16_t calibrateFreshAir()
{
return Request("G");
}
void setOperatingMode(uint8_t mode)
{
sprintf(buffer, "K %u", mode);
Command(buffer);
}
Also take quick note of the two #define statements at the top of the sketch. When you first unbox your sensor you may need to configure it using setOperatingMode(). This sketch is designed to work in polling mode. If you've successfully calibrated and are reading CO2 levels to the terminal you're ready to move on to publishing this to the cloud. Let's connect to Adafruit IO and start visualizing the data.
Publishing CO2 metrics to the cloudIf you haven't connected to Adafruit IO using anduinoWiFi yet, check out this project writeup that will get started. It covers all the details I'm going to gloss over here. Here's the sketch to get you started publishing your CO2 levels every minute.
#include <WiFi101.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include "AnduinoLCD.h"
// WiFi parameters
#define WLAN_SSID "YOUR_SSID"
#define WLAN_PASS "YOUR_PASSWD"
// Adafruit IO
#define AIO_SERVER "io.adafruit.com"
#define AIO_SERVERPORT 1883
#define AIO_USERNAME "YOUR_AIO_USERNAME"
#define AIO_KEY "YOUR_AIO_KEY"
WiFiClient client;
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);
/****************************** Feeds ***************************************/
// Setup feed for co2
Adafruit_MQTT_Publish carbonDioxide = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/co2");
/*Create an instance of the AnduinoLCD */
AnduinoLCD LCD = AnduinoLCD(ST7735_CS_PIN, ST7735_DC_PIN, 13);
static int co2 = 0;
static int co2Prev = 0;
#define CZR_STREAMING 0x01
#define CZR_POLLING 0x02
char buffer[20] = {0};
void setup() {
Serial.begin(115200);
delay(3000);
Serial1.begin(9600);
//Connect to WiFi & Adafruit.IO
connectToWiFi();
connectToAdafruit();
//Initialize LCD
LCD.begin();
LCDinit();
//CO2 Calibration and initial setup
//setOperatingMode(CZR_STREAMING);
//setOperatingMode(CZR_POLLING);
//calibrateFreshAir();
}
void loop() {
// ping adafruit io a few times to make sure we remain connected
if(! mqtt.ping(3)) {
// reconnect to adafruit io
if(! mqtt.connected())
connect();
}
// Grab the current co2 reading
co2 = Request("Z");
//convert int temp to char array
char b[20];
String str;
str=String(co2);
for(int i=0; i<str.length(); i++)
{
b[i]=str.charAt(i);
}
b[(str.length())+1]=0;
// Publish data
if (!carbonDioxide.publish((char*)b)) {
Serial.println(F("Failed to publish co2"));
} else {
Serial.print(F("co2 published: "));
Serial.println(co2);
displayCo2(co2, co2Prev);
}
Serial.print("CO2 : ");Serial.println(co2);
Serial.println("");
//prev val stored for LCD
co2Prev = co2;
//repeat every 1min
delay(60000);
}
// connect to adafruit io via MQTT
void connect() {
Serial.print(F("Connecting to Adafruit IO... "));
int8_t ret;
while ((ret = mqtt.connect()) != 0) {
switch (ret) {
case 1: Serial.println(F("Wrong protocol")); break;
case 2: Serial.println(F("ID rejected")); break;
case 3: Serial.println(F("Server unavail")); break;
case 4: Serial.println(F("Bad user/pass")); break;
case 5: Serial.println(F("Not authed")); break;
case 6: Serial.println(F("Failed to subscribe")); break;
default: Serial.println(F("Connection failed")); break;
}
if(ret >= 0)
mqtt.disconnect();
Serial.println(F("Retrying connection..."));
delay(5000);
}
Serial.println(F("Adafruit IO Connected!"));
}
void displayCo2(int co2, int co2Prev)
{
//clear the stale value
LCD.setTextColor(ST7735_BLACK);
LCD.setTextSize(2);
LCD.setTextWrap(true);
LCD.setCursor(40,60);
LCD.setTextSize(3);
LCD.print(co2Prev);
LCD.setTextSize(1);
LCD.print("ppm");
//Print new value
LCD.setTextColor(ST7735_WHITE);
LCD.setTextSize(2);
LCD.setTextWrap(true);
LCD.setCursor(40,60);
LCD.setTextSize(3);
LCD.print(co2);
LCD.setTextSize(1);
LCD.print("ppm");
}
void connectToWiFi()
{
// Connect to WiFi access point.
delay(10);
Serial.print(F("Connecting to "));
Serial.println(WLAN_SSID);
WiFi.begin(WLAN_SSID, WLAN_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(F("."));
}
Serial.println(F("WiFi connected!"));
}
void connectToAdafruit()
{
// connect to adafruit io
connect();
}
void LCDinit()
{
LCD.setBacklight(ON);
LCD.fillScreen(ST7735_BLACK); //clear the screen
LCD.showBanner(); //load Andium Banner
LCD.setTextColor(ST7735_WHITE);
LCD.setTextSize(2);
LCD.setTextWrap(true);
LCD.setCursor(0,40);
LCD.print("CO2: ");
}
uint16_t Request(char* s)
{
buffer[0] = '\0';
int idx = 0;
//send command request 'Z' for CO2
Command(s);
delay(250);
while(Serial1.available())
{
buffer[idx++] = Serial1.read();
}
buffer[idx] = '\0';
uint16_t rv = 0;
rv = atoi(&buffer[2]);
return rv;
}
void Command(char* s)
{
Serial1.print(s);
Serial1.print("\r\n");
}
uint16_t calibrateFreshAir()
{
return Request("G");
}
void setOperatingMode(uint8_t mode)
{
sprintf(buffer, "K %u", mode);
Command(buffer);
}
That's it!This thing is super sensitive, you can nearly track room occupancy levels based off the concentration of CO2 in even a well vented room. Time to prove to your professor that it isn't 'diff eq' getting you down, it's the poor ventilation! Time to move class to the beach!
Comments