viliamk
Published © LGPL

CO2Cube warns about exhaled air and opens a window

The portable cube with a CO2 sensor that opens a window when an air in the room is exhaled

BeginnerFull instructions provided154
CO2Cube warns about exhaled air and opens a window

Things used in this project

Hardware components

Arduino Nano
×1

Story

Read more

Code

CO2Cube

Arduino
Main loop
/*
 * 300-400 ppm /v mestách aj viac/  koncentrácia CO2 vo vonkajšom prostredí
 * do 1000 ppm  zdravotne akceptovateľná koncentrácia CO2
 * 1000-1200 ppm  maximálna akceptovateľná úroveň CO2 v interiéri
 * 1200-1500 ppm  pocit vydýchaného vzduchu, začínajú ťažkosti /únava/
 * 1500-2000 ppm  znížená koncentrácia, únava, ospalosť
 * 2000-5000 ppm  strata pozornosti, zvýšená srdcová frekvencia, bolesti hlavy
 * nad 25000 ppm  začína hroziť smrť udusením
*/

#define buttonPin 2
#define acceptedCO2 1100
#define stopAirCO2 800
#define maxTransmitAttempts 5
#define cycle30mins 225          //30min x 60sec / 8sec sleep = 225x cycle
#define cycle20hours 9000       //20h x 60min x 60sec / 8sec sleep = 9000x cycle

int CO2Value[3] = {0, 0, 0};
int minCO2Value, maxCO2Value;
int tempValue, prevTempValue, tempCount = 5;
int getCO2;
byte countCO2Value = 0;
volatile bool buttonPushed = false; 
bool start = false, openWindow = false;
bool transmitFailed = false;
char transmitText[10];
byte transmitAttempt = maxTransmitAttempts;
long cycle = 0;

void setup() {
  InitializeLowPower();
  InitializeCO2Cube();
  InitializeSpeaker();
  InitializeOledDisplay();
  InitializeTemperature();
  InitializeCO2Sensor();
  InitializeTransmitter();
  InitializeEEPROM();
  PlayWarn();
}

void InitializeCO2Cube() {
  pinMode(10, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(buttonPin), PushButton, LOW);
}

void loop() {
  if (buttonPushed) {
    detachInterrupt(digitalPinToInterrupt(buttonPin));
    DisplayValues(tempValue, CO2Value[countCO2Value]);
    TurnDisplayON();   
    TransmitText(String(CO2Value[countCO2Value]).c_str());
    SleepTime(1);
    if (!start) TurnDisplayOFF();
    buttonPushed = false;
    attachInterrupt(digitalPinToInterrupt(buttonPin), PushButton, LOW);
  }

  tempCount++;
  if (tempCount > 5) {                 //saves some energy not to get the temp every time
    tempValue = GetTemperature();
    if (tempValue != prevTempValue) SetTemp(tempValue);
    tempCount = 0;
  }

  getCO2 = GetCO2Value();
  if (getCO2 > 0) {
    countCO2Value++; if (countCO2Value > 2) countCO2Value = 0;
    CO2Value[countCO2Value] = getCO2; 
  
    minCO2Value = min(CO2Value[0], CO2Value[1]);
    minCO2Value = min(minCO2Value, CO2Value[2]);
  
    maxCO2Value = max(CO2Value[0], CO2Value[1]);
    maxCO2Value = max(maxCO2Value, CO2Value[2]);
  }
  
  if (!start && (minCO2Value > acceptedCO2)) {   //initializes to show high values of CO2
    start = true;
    DisplayValues(tempValue, CO2Value[countCO2Value]);
    TurnDisplayON();
    PlayWarn();
    OpenWindow(true);
  }
    
  if (start && (maxCO2Value > acceptedCO2)) {   //shows high values of CO2
    DisplayValues(tempValue, CO2Value[countCO2Value]);
  }
  else {      //if values of CO2 are acceptable
    start = false;
    TurnDisplayOFF();
  }

  if (openWindow && (maxCO2Value < stopAirCO2)) OpenWindow(false);

  if (transmitFailed && (transmitAttempt < maxTransmitAttempts)) {
    transmitFailed = !TransmitText(transmitText);
    transmitAttempt++;
  }

  cycle++;
  if (cycle > cycle20hours) {   //cycle30mins, cycle20hours
    
    //Use this part after 20 hours when the baseline was already set for the first time
    cycle = 0;
    DisplayText("Setting baseline");
    PlayWarn();
    WriteBaselineToEEPROM();
    ResetCO2Sensor();
    OpenWindow(false);

    /*
    //Use this part for the first set of the baseline after 30 minutes on the fresh air
    cycle = 0;
    WriteBaselineToEEPROM();
    DisplayText("Baseline set");
    PlayWarn();
    while(1);
    */
  }
  
  if (!buttonPushed) SleepTime(8);
}

void OpenWindow(bool b) {
  openWindow = b;
  strcpy(transmitText, b? "open" : "close");
  transmitFailed = true;
  transmitAttempt = 0;
}

void PushButton() {
  buttonPushed = true;
}

CO2Sensor

Arduino
To operate CCS811 sensor
/*
 * CCS811 sensor
 * SDA - pin A4
 * SCK - pin A5
 * VCC - 3.3V
 * WAKE - GND or D5 to control SLEEP MODE
 */
 
#include <Adafruit_CCS811.h> 

/*
enum {CCS811_DRIVE_MODE_IDLE = 0x00, CCS811_DRIVE_MODE_1SEC = 0x01, CCS811_DRIVE_MODE_10SEC = 0x02, CCS811_DRIVE_MODE_60SEC = 0x03, CCS811_DRIVE_MODE_250MS = 0x04 };
*/

#define pinCO2Wake 5

Adafruit_CCS811 ccs;

void InitializeCO2Sensor() {
  pinMode(pinCO2Wake, OUTPUT);
  digitalWrite(pinCO2Wake, LOW);
  if (!ccs.begin()) {
    DisplayText("Failed start CO2 sensor");
    PlayError();
    while(1);
  }
  while(!ccs.available());
  //calculate internal temperature
  float temp = ccs.calculateTemperature();
  ccs.setTempOffset(temp - 25.0);
  
  int i = ReadBaselineFromEEPROM();
  ccs.setBaseline(i);
  
  ccs.setDriveMode(CCS811_DRIVE_MODE_10SEC);
}

void WakeUpCO2Sensor() {
  digitalWrite(pinCO2Wake, LOW);
  delay(10);
}

void SleepCO2Sensor() {
  digitalWrite(pinCO2Wake, HIGH);
}

int GetCO2Value() {
  int value = 0;
  WakeUpCO2Sensor();
  while ((value == 0) && !buttonPushed) {    //wait till sensor gives value
    if (ccs.available()) {
      if(!ccs.readData()) value = ccs.geteCO2();
    }
  }
  SleepCO2Sensor();
  return value;
}

void SetTemp(int tempVal) {
  WakeUpCO2Sensor();
  while (!ccs.available() && !buttonPushed);
  ccs.setEnvironmentalData(50, tempVal);    //humidity + temperature
  prevTempValue = tempVal;
  SleepCO2Sensor();
}

int ReadCO2Baseline() {
  return ccs.getBaseline();
}

void ResetCO2Sensor() {
  ccs.SWReset();
  InitializeCO2Sensor();
  DisplayText("Baseline set");
  PlayWarn();
}

OLEDDisplay

Arduino
To operate OLED display
/*
 * OLED display 128x32
 * SDA - pin A4
 * SCK - pin A5
 * VCC - 3V-5V
 */

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void InitializeOledDisplay() {
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    PlayError();
    while(1);
  }
  display.dim(true);  // set contrast to 31 (reduces consumption to half)
  DisplayIntro();
  //SleepTime(1);
}

#define shift 55
#define vertical 12

void DisplayValues(int tempVal, int CO2Val) {
  display.clearDisplay();
  display.setTextColor(WHITE);

  display.setCursor(0,0);   //temperature
  display.setTextSize(1);
  display.print("Temp:");  
  display.setCursor(0,vertical);
  display.setTextSize(2); 
  display.print(tempVal); 
  display.setTextSize(1);
  display.print("O");
  display.setTextSize(2);
  display.print("C");

  display.setCursor(shift,0);   //CO2
  display.setTextSize(1);
  display.print("CO2:");
  display.setCursor(shift,vertical);
  display.setTextSize(3);
  display.print(CO2Val);    
  
  display.display();
}

void DisplayIntro() {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setCursor(0,8);   
  display.setTextSize(2);
  display.println(" MyCO2Cube");
  display.setTextSize(1);
  display.print("           by Viliam");
  display.display();
}

void DisplayText(char* text) {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setCursor(0,0);   
  display.setTextSize(1);
  display.print(text);
  display.display();
  TurnDisplayON();
}

void TurnDisplayOFF() {
  display.ssd1306_command(SSD1306_DISPLAYOFF); 
}

void TurnDisplayON() {
  display.ssd1306_command(SSD1306_DISPLAYON); 
}

PIN_scheme

Arduino
/* CO2Cube
 * PIN 0 
 * PIN 1
 * PIN 2 - (INPUT) PushButton wakes up Arduino
 * PIN 3 
 * PIN 4 
 * PIN 5 - (OUTPUT) Wakes up CCS811 sensor
 * PIN 6 - CSN nRF24L01
 * PIN 7 - CE nRF24L01
 * PIN 8 - Temperature sensor KY-001
 * PIN 9 - (OUTPUT) Speaker
 * PIN 10
 * PIN 11 - MOSI nRF24L01
 * PIN 12 - MISO nRF24L01
 * PIN 13 - SCK nRF24L01
 * PIN A0 
 * PIN A1 
 * PIN A2 
 * PIN A3 
 * 
 * PIN A4 - (SDA) CCS811 sensor, OLED display
 * PIN A5 - (SCL) CCS811 sensor, OLED display
 * 
 * PIN A6 
 * PIN A7
 * 
 * GND - battery, Arduino, KY-001, CCS811, Speaker, PushButton, nRF24L01
 * VCC 3.3 - battery, switch, Arduino, CCS811, nRF24L01, KY-001
 */

Transmitter

Arduino
Serves nRF24L01 transmitter
/*
 * nRF24L01
 */

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 transmitter(7, 6);  // CE, CSN
const byte address[6] = "openw";   //address through which two modules communicate. It can be any text of 6 chars

/*
 * You can try a few software tricks to improve range as well, referenced in the setup function of both Arduino code examples. Power levels are defined by the radio.setPALevel(), and vary between RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, and RF24_PA_max.

1. Set the data rates with the radio.setDataRate() function. RF24_250KBPS signifies 250 kbps, which is the slowest speed available and it offers the longest range of data transmission. RF24_1MBPS signifies 1Mbps and RF24_2MBPS signifies 2 Mbps, giving a higher transfer speed, but less range.

2. You can also set the channel of your radio between 2.400 and 2.524 GHz using the radio.setChannel(). At that reading, values of 0-124 correspond to 2.4 GHz plus the channel number in units of MHz. So radio.setChannel(21). Your radio will therefore communicate at 2.421 GHz. While this is a bit more nebulous speed-wise, different channels will certainly give better performance depending on your surrounding environment. Note that 2.483.5 GHz is normally the top legal limit for this type of transmission, so be sure to keep that in mind.
 */
 
void InitializeTransmitter() {
  transmitter.begin();
  
  transmitter.setPALevel(RF24_PA_MIN);    //RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, and RF24_PA_max
  transmitter.setDataRate(RF24_250KBPS);  //RF24_250KBPS, RF24_1MBPS, RF24_2MBPS
  //transmitter.setChannel(0);            //max 80
  
  transmitter.openWritingPipe(address);
  transmitter.stopListening();            //Set module as transmitter
  TransmitText("Hello");
}

bool TransmitText(char msg[10]) {
 bool succesfull = false;
 byte count = 0;
 
 transmitter.powerUp();
 while (!succesfull && (count < 5)) {
   succesfull = transmitter.write(msg, strlen(msg));
   count++;
   if (!succesfull) delay(100);
 }
 transmitter.powerDown();
 return succesfull;
}

LOWPower

Arduino
Low Power sheet for decreasing consumption
#include <LowPower.h>

/*
enum period_t {
    SLEEP_15MS, SLEEP_30MS, SLEEP_60MS, SLEEP_120MS, SLEEP_250MS, SLEEP_500MS, SLEEP_1S, SLEEP_2S, SLEEP_4S, SLEEP_8S, SLEEP_FOREVER};
*/

void InitializeLowPower() {
  SetClockTo8MHz();   //decrease the consumption of Arduino
}

void SleepTime(byte sleep) {    //puts Arduino into sleep mode
  switch(sleep) {
    case 2:
      LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
      break;
    case 4:
      LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF);
      break;
    case 8:
      LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
      break;
    default:
      LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
      break;
  }
}

void SetClockTo8MHz() {
  noInterrupts();
  CLKPR = 0x80; // (1000 0000) enable change in clock frequency
  CLKPR = 0x01; // (0000 0001) use clock division factor 2 to reduce the frequency from 16 MHz to 8 MHz, 0x02 is to 4 Mhz, ...
  interrupts();
}

Speaker

Arduino
To operate a speaker
#define speakerPin 9
#define toneDelay 40
#define pauseDelay 50

void InitializeSpeaker() {
  pinMode(speakerPin, OUTPUT);
}

void Play(int sound) {
  tone(speakerPin, sound, toneDelay);
  delay(pauseDelay);
}

void PlayError() {
  Play(500);
  Play(700);
  Play(500);
  Play(700);
  Play(500);
  Play(700);
  Play(500);
  Play(700);
  Play(500);
}

void PlayWarn() {
  Play(1600);
  Play(800);
  Play(2400);
  Play(3000);
  Play(3500);
}

Temperature

Arduino
To operate the temperature sensor
/*
 * Temperature module KY-001
 */

#include <OneWire.h>
#include <DallasTemperature.h>

#define tempPin 8

OneWire oneWire(tempPin);
DallasTemperature tempSensor(&oneWire);

void InitializeTemperature() {
  tempSensor.begin();
}

float GetTemperature() { 
  tempSensor.requestTemperatures(); // Send the command to get temperatures
  return tempSensor.getTempCByIndex(0);   //originaly float
}

EEPROM

Arduino
Saves CO2 baseline
#include <EEPROM.h>

#define baselineAddress 100

void InitializeEEPROM() {
}

void WriteBaselineToEEPROM() {
  int baseline = ReadCO2Baseline();
  EEPROM.update(baselineAddress, baseline >> 8);
  EEPROM.update(baselineAddress + 1, baseline && 0xFF);
}

int ReadBaselineFromEEPROM() {
  return (EEPROM.read(baselineAddress) << 8) + EEPROM.read(baselineAddress + 1);
}

Credits

viliamk

viliamk

14 projects • 5 followers

Comments