abhi gopal
Created July 11, 2018

AirLYTICS

Compact and reliable design to monitor the air quality of a room wirelessly

339
AirLYTICS

Things used in this project

Hardware components

Adafruit BME 680
×1
Flora RGB Neopixel LEDs- Pack of 4
Adafruit Flora RGB Neopixel LEDs- Pack of 4
×1
CSMS15CIC05CT-ND
×1
Arduino UNO
Arduino UNO
×1
Helium Starter Kit (LEGACY)
Helium Starter Kit (LEGACY)
×1

Software apps and online services

Arduino IDE
Arduino IDE
Google DataStudio
Cloud IoT Core
Google Cloud IoT Core

Hand tools and fabrication machines

FreeCAD
Simplify3D

Story

Read more

Custom parts and enclosures

Bottom Enclosure for Helium Atom and Arduino

This was designed in FreeCAD

Top Lid Cover

This was designed in freeCAD

Schematics

Schematic for Sensors and LEDs

This is to provide an idea how sensor pins were connected and LEDs were connected.

Code

IOT core

C/C++
The code includes the main file IOT core and the two header files Board.h and Timehelpers.h
//Author :Hemant Gopalsing
/*****************************************************************INCLUDES******************************************************************/
#include <Time.h>
#include <TimeLib.h>
#include "Arduino.h"
#include "Board.h"
#include "Helium.h"
#include "HeliumUtil.h"
#include <Adafruit_NeoPixel.h>
#include <math.h>
#include <Wire.h>
#include <avr/wdt.h>
#include "BlueDot_BME680.h"
#include <ArduinoJson.h>

/******************************************************************BME680*******************************************************************/
BlueDot_BME680 bme680 = BlueDot_BME680();
void BMEsetup() {
  bme680.parameter.I2CAddress = 0x77;                  // Choose I2C Address
  bme680.parameter.sensorMode = 0b01;                  // Default sensor mode = Single measurement, then sleep
  bme680.parameter.IIRfilter = 0b100;                  // Setting IIR Filter coefficient to 15 (default)
  bme680.parameter.humidOversampling = 0b101;          // Setting Humidity Oversampling to factor 16 (default)
  bme680.parameter.tempOversampling = 0b101;           // Setting Temperature Oversampling factor to 16 (default)
  bme680.parameter.pressOversampling = 0b000;          // factor 0 = Disable pressure measurement
  if (bme680.init() != 0x61)
  {
    Serial.print(F("BME680 could not be found"));
  }
}

/*****************************************************************CHANNELS******************************************************************/
#define CHANNEL_NAME "Google Cloud IoT Core"
#define Max_Reconnect_Time 30
Helium  helium(&atom_serial);
Channel channel(&helium);
#define PIN_neo1 4                                                              
#define PIN_neo2 7
#define NUMPIXELS 1


int delayval = 500;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN_neo1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixels2 = Adafruit_NeoPixel(NUMPIXELS, PIN_neo2, NEO_GRB + NEO_KHZ800);

void channel_connect() {
  int count = 0;
  helium.begin(9600);
  while (!helium.connected()) {
    //Serial.print("test3");
    neoSetup();
    neoColor(200, 200, 200, 50, pixels2);
    int status = helium.connect();
    report_status(status);
    if (helium_status_OK != status) {
      delay(1000);
    }
    Serial.print("test3");
  }
  
  channel_create(&channel, CHANNEL_NAME);
  neoSetup();
  neoColor(200, 0, 200, 1000, pixels2);
}

void
channel_send(const char * channel_name, void const * data, size_t len)
{
    int    status;
    int8_t result;

    do
    {
        // Try to send
        Serial.print(F("Sending - "));
        status = channel.send(data, len, &result);
        report_status(status, result);
        // Create the channel if any service errors are returned
        if (status == helium_status_OK && result != 0)
        {
            channel_connect();
        }
        else if (status != helium_status_OK)
        {
            delay(1000);
        }
    } while (helium_status_OK != status || result != 0);
}

/****************************************************************JSON STRING****************************************************************/
#define CHANNEL_DELAY 1000
#define CHANNEL_SEND_CYCLE 12
int channel_counter = 0;
double temperature;
double humidity;
double CO_PPM;
int indexing;

void JSONformat(int index) {
 bme680.writeCTRLMeas();
 temperature = bme680.readTempC();
 humidity = bme680.readHumidity();
 CO_PPM=calc_PPM_CO();
 indexing =index;
  if (--channel_counter <= 0)
  {
    StaticJsonBuffer < JSON_OBJECT_SIZE(2) + 100 > jsonBuffer;
    JsonObject & root = jsonBuffer.createObject();

     root[F("Temperature")] = temperature;
    root[F("Humidity")] = humidity;
     root[F("CO_PPM")] =  CO_PPM;
     root[F("index")]  = indexing;

        char   buffer[HELIUM_MAX_DATA_SIZE];
        size_t used = root.printTo(buffer, HELIUM_MAX_DATA_SIZE);

        channel_send(CHANNEL_NAME, buffer, used);

        channel_counter = CHANNEL_SEND_CYCLE;
          }

    delay(CHANNEL_DELAY);
}

/********************************************************************FAN********************************************************************/
#define fan_PIN 13
 
void fan_on(int timedelay) {
  digitalWrite(fan_PIN, LOW);
  delay(timedelay);
  digitalWrite(fan_PIN, HIGH);
  delay(timedelay);
}

/******************************************************************BME680*******************************************************************/
#define buzzer_PIN 6
void buzzerON() {
  pinMode(buzzer_PIN, OUTPUT);
  TCCR0A = 0; //reset the register
  TCCR0B = 0; //reset tthe register
  TCCR0A = 0b10100011; // fast pwm mode
  TCCR0B = 0b00000011; // prescaler 64
  OCR0A = 50; //duty cycle for pin 6
  OCR0B = 50; //duty cycle for pin 5
}


/*****************************************************************NEOPIXEL******************************************************************/
/* This section sets up the neoPixel to display warning lights based on the PPM value.
    The recommended PPM values for dectectable gases are :i) CO --------> < 10 PPM
                                                          ii)H2 --------> < 10 PPM

*/
void neoSetup() {
  pixels.begin();
  pixels2.begin();
}

void neoColor(int R, int G, int B, int wait, Adafruit_NeoPixel pixel) {
  neoSetup();
  pixel.setPixelColor(0, pixel.Color(R, G, B));
  pixel.show();
  delay(wait);
  pixel.setPixelColor(0, pixel.Color(0, 0, 0));
  pixel.show();
  delay(wait);
}

void neoColorPPM(double PPM, Adafruit_NeoPixel pixels) {
  neoSetup();
  if (PPM < 2) {
    pixels.setPixelColor(0, pixels.Color(0, 200, 0));
  }
  else if ((PPM > 2) && (PPM < 5)) {
    neoColor(200, 200, 0, 50, pixels);
  }
  else if (PPM > 5) {
    neoColor(200, 0, 0, 30, pixels);
  }
  pixels.show();
}


/****************************************************************MCIS5524IAQ****************************************************************/

/*The MCIS sensor is sensitive to : i) Carbon Monoxide            (0-100PPM)
                                    ii)Volatile Organic compounds incuding methane (0-1000 PPM)
   At the beginning it is important to place the sensor in a clean air condition so that it can callibrate itself
   Once callibrated the sensor can measure gas resistance at the current conditions.
   The adafruit sensor has a 10 kOhm pulldown at the analog output. The gas resistance RS and the 10Kohm forms a
   voltage dividor and we can calculate RS.
   The ratio RS/RO can be calculated from those values.
   The graph of RS/RO was plotted using the graph from the datasheet and after the graph was linearized using LN the following
   relationship was obtained: i) CO ------> Y = -1.884 X + 1.502 , where Y = ln(PPM) and X = ln(RS/RO)
*/

#define ADC_voltdiv 0.00488
#define Vsupply 5
#define Analog_pulldown 10
#define Ro_correction_factor 0.10
#define eps 50
#define max_Ro_deviation 50
#define Ro_cycles 5

#define CO_threshold 10
#define Ethanol_threshold 80


double Ro_callibrated;
double Rs;
int   Raw_ADCvalue;

double calcRs() {
  Raw_ADCvalue = analogRead(A0);
  Rs = ( ((Vsupply / (ADC_voltdiv * Raw_ADCvalue)) - 1)) * Analog_pulldown;
  return Rs;
}

double recheckCO() {
  double sum;
  double temp_PPM;
  for (int i = 0; i < 5; i++) {
    fan_on(200);
    sum += calc_PPM_CO();
  }
  temp_PPM = sum / 5;
  return temp_PPM;
}
double recheckethanol() {
  double sum;
  double temp_PPM;
  for (int i = 0; i < 5; i++) {
    fan_on(200);
    sum += calc_Ethanol();
  }
  temp_PPM = sum / 5;
  return temp_PPM;
}

double calc_PPM_CO() {
  double ratio = calcRs() / Ro_callibrated;
  double log_ratio = log(ratio);
  double log_PPM   = -1.1884 * (log_ratio) + 1.502;
  double PPM_val = exp(log_PPM);
  Serial.println(PPM_val);
  if (PPM_val > CO_threshold) {
    PPM_val = recheckCO();
    Serial.println((PPM_val));
  }
  if (PPM_val>CO_threshold){
    buzzerON();
    }
  neoColorPPM(PPM_val,pixels);
 
  return PPM_val;
}


double calc_Ethanol() {
  double ratio = calcRs() / Ro_callibrated;
  double log_ratio = log(ratio);
  double log_PPM2   =  -1.53*(log_ratio) + 0.533;
  double PPM_val2 = exp(log_PPM2);
  Serial.println(PPM_val2);
  if (PPM_val2 > Ethanol_threshold) {
    PPM_val2 = recheckethanol();
    Serial.println((PPM_val2));
  }
  if (PPM_val2>Ethanol_threshold){
    buzzerON();
    }
  neoColorPPM(PPM_val2,pixels);
 
  return PPM_val2;
}




boolean Ro_Check() {
  if (abs(Ro_callibrated - max_Ro_deviation) > eps) {
    return false;
  }
  return true;
}

void callibrate_Ro() {
  Ro_callibrated = calcRs() * Ro_correction_factor;
  Serial.println(calcRs());
  Serial.print(("Ro is callibrated at:"));
  Serial.println((Ro_callibrated));
  float sumRo = 0;
  int count = 0;
  while (Ro_Check() == false) {
    Serial.println(("Callibrated value is not within range. Recallibrating...!"));
    for (int i = 0; i++; i < Ro_cycles) {
      fan_on(2000);
      sumRo += calcRs() * Ro_correction_factor;
    }
    delay (1000);
    Ro_callibrated = sumRo / Ro_cycles;
    count++;
    if (count > 10) {
      neoColor(100, 100, 100, 500, pixels);
      Serial.println(F("Please put device in fresh air and restart it!!!."));
      while (1);
    }
  }
}

/*******************************************************************SETUP*******************************************************************/
void setup() {
  
  digitalWrite(fan_PIN,HIGH);
  Serial.begin(9600);
  neoSetup();
  neoColor(0, 0, 200, 1000, pixels2);
  pinMode(fan_PIN,OUTPUT);
  BMEsetup();
  callibrate_Ro();
  channel_connect();
  
}

int count=0;
void loop() {
  count++;
  JSONformat(count);
  delay(500);
}

Board Header file

C/C++
This is required to run the main code
/*
 * Copyright 2017, Helium Systems, Inc.
 * All Rights Reserved. See LICENCE.txt for license information
 */

#ifndef BOARD_H
#define BOARD_H


#if defined(ARDUINO_AVR_UNO)
#include "SoftwareSerial.h"
SoftwareSerial atom_serial(8, 9);
#define HELIUM_BAUD_RATE helium_baud_b9600

#elif defined(ARDUINO_AVR_MEGA2560)
#include "SoftwareSerial.h"
SoftwareSerial atom_serial(10,11);
#define HELIUM_BAUD_RATE helium_baud_b9600 

#elif defined(ARDUINO_SAM_ZERO)
// Arduino M0 Pro
#define atom_serial Serial5

#elif defined(ARDUINO_SAMD_ZERO)
// Arduino Zero
#define atom_serial Serial1

#elif defined(ARDUINO_SAM_DUE)
// Arduino Due with Serial3 (pin 15, 14)
// mapped to pin 8, 9 on the adapter
#define atom_serial Serial3
#endif

#if defined(CORE_TEENSY)
//Teensy with Serial1 (pin 0, 1)
#define atom_serial Serial1
extern "C"{

    int _write(int f, char *ptr,int len){
        int i;
        for(i=0;i<len;i++)
            {
                atom_serial.write(*ptr++);
            }
        return len;
    }
    int _read (int f, char *ptr, int len)
    {
        *ptr=atom_serial.read();  
        
        return len;
    }
}

#elif defined(ARDUINO_AVR_PRO)
//ProMini/Micro with Serial pins (8,9)
#include "SoftwareSerial.h"
SoftwareSerial atom_serial(8,9);

#endif

#ifndef HELIUM_BAUD_RATE
#define HELIUM_BAUD_RATE helium_baud_b115200
#endif

#endif // BOARD_H

Timerhelpers Header file

C/C++
This is used to break time components in code.
#ifndef _TimerHelpers_h
#define _TimerHelpers_h

#if defined(ARDUINO) && ARDUINO >= 100
  #include "Arduino.h"
#else
  #include "WProgram.h"
#endif

/* ---------------------------------------------------------------
 Timer 0 setup
 --------------------------------------------------------------- */

namespace Timer0 
{
  // TCCR0A, TCCR0B
  const byte Modes [8] [2] = 
  {
  
  { 0,                         0 },            // 0: Normal, top = 0xFF
  { _BV (WGM00),               0 },            // 1: PWM, Phase-correct, top = 0xFF
  {               _BV (WGM01), 0 },            // 2: CTC, top = OCR0A
  { _BV (WGM00) | _BV (WGM01), 0 },            // 3: Fast PWM, top = 0xFF
  { 0,                         _BV (WGM02) },  // 4: Reserved
  { _BV (WGM00),               _BV (WGM02) },  // 5: PWM, Phase-correct, top = OCR0A
  {               _BV (WGM01), _BV (WGM02) },  // 6: Reserved
  { _BV (WGM00) | _BV (WGM01), _BV (WGM02) },  // 7: Fast PWM, top = OCR0A
  
  };  // end of Timer0::Modes
  
  // Activation
  // Note: T0 is pin 6, Arduino port: D4
  enum { NO_CLOCK, PRESCALE_1, PRESCALE_8, PRESCALE_64, PRESCALE_256, PRESCALE_1024, T0_FALLING, T0_RISING };
  
  // what ports to toggle on timer fire
  enum { NO_PORT = 0, 
    
    // pin 12, Arduino port: D6
    TOGGLE_A_ON_COMPARE  = _BV (COM0A0), 
    CLEAR_A_ON_COMPARE   = _BV (COM0A1), 
    SET_A_ON_COMPARE     = _BV (COM0A0) | _BV (COM0A1),
    
    // pin 11, Arduino port: D5
    TOGGLE_B_ON_COMPARE  = _BV (COM0B0), 
    CLEAR_B_ON_COMPARE   = _BV (COM0B1), 
    SET_B_ON_COMPARE     = _BV (COM0B0) | _BV (COM0B1),
  };
  
  
  // choose a timer mode, set which clock speed, and which port to toggle
  void setMode (const byte mode, const byte clock, const byte port)
  {
  if (mode < 0 || mode > 7)  // sanity check
    return;
  
  // reset existing flags
  TCCR0A = 0;
  TCCR0B = 0;
  
  TCCR0A |= (Modes [mode] [0]) | port;  
  TCCR0B |= (Modes [mode] [1]) | clock;
  }  // end of Timer0::setMode
  
}  // end of namespace Timer0 

/* ---------------------------------------------------------------
 Timer 1 setup
 --------------------------------------------------------------- */

namespace Timer1 
{
  // TCCR1A, TCCR1B
  const byte Modes [16] [2] = 
  {
  
  { 0,                         0 },            // 0: Normal, top = 0xFFFF
  { _BV (WGM10),               0 },            // 1: PWM, Phase-correct, 8 bit, top = 0xFF
  {               _BV (WGM11), 0 },            // 2: PWM, Phase-correct, 9 bit, top = 0x1FF
  { _BV (WGM10) | _BV (WGM11), 0 },            // 3: PWM, Phase-correct, 10 bit, top = 0x3FF
  { 0,                         _BV (WGM12) },  // 4: CTC, top = OCR1A
  { _BV (WGM10),               _BV (WGM12) },  // 5: Fast PWM, 8 bit, top = 0xFF
  {               _BV (WGM11), _BV (WGM12) },  // 6: Fast PWM, 9 bit, top = 0x1FF
  { _BV (WGM10) | _BV (WGM11), _BV (WGM12) },  // 7: Fast PWM, 10 bit, top = 0x3FF
  { 0,                                       _BV (WGM13) },  // 8: PWM, phase and frequency correct, top = ICR1    
  { _BV (WGM10),                             _BV (WGM13) },  // 9: PWM, phase and frequency correct, top = OCR1A    
  {               _BV (WGM11),               _BV (WGM13) },  // 10: PWM, phase correct, top = ICR1A    
  { _BV (WGM10) | _BV (WGM11),               _BV (WGM13) },  // 11: PWM, phase correct, top = OCR1A
  { 0,                         _BV (WGM12) | _BV (WGM13) },  // 12: CTC, top = ICR1    
  { _BV (WGM10),               _BV (WGM12) | _BV (WGM13) },  // 13: reserved
  {               _BV (WGM11), _BV (WGM12) | _BV (WGM13) },  // 14: Fast PWM, TOP = ICR1
  { _BV (WGM10) | _BV (WGM11), _BV (WGM12) | _BV (WGM13) },  // 15: Fast PWM, TOP = OCR1A
  
  };  // end of Timer1::Modes
  
  // Activation
  // Note: T1 is pin 11, Arduino port: D5
  enum { NO_CLOCK, PRESCALE_1, PRESCALE_8, PRESCALE_64, PRESCALE_256, PRESCALE_1024, T1_FALLING, T1_RISING };
  
  // what ports to toggle on timer fire
  enum { NO_PORT = 0, 
    
    // pin 15, Arduino port: D9
    TOGGLE_A_ON_COMPARE  = _BV (COM1A0), 
    CLEAR_A_ON_COMPARE   = _BV (COM1A1), 
    SET_A_ON_COMPARE     = _BV (COM1A0) | _BV (COM1A1),
    
    // pin 16, Arduino port: D10
    TOGGLE_B_ON_COMPARE  = _BV (COM1B0), 
    CLEAR_B_ON_COMPARE   = _BV (COM1B1), 
    SET_B_ON_COMPARE     = _BV (COM1B0) | _BV (COM1B1),
  };
  
  // choose a timer mode, set which clock speed, and which port to toggle
  void setMode (const byte mode, const byte clock, const byte port)
  {
  if (mode < 0 || mode > 15)  // sanity check
    return;
  
  // reset existing flags
  TCCR1A = 0;
  TCCR1B = 0;
  
  TCCR1A |= (Modes [mode] [0]) | port;  
  TCCR1B |= (Modes [mode] [1]) | clock;
  }  // end of Timer1::setMode
  
}  // end of namespace Timer1 

/* ---------------------------------------------------------------
 Timer 2 setup
 --------------------------------------------------------------- */

namespace Timer2 
{
  // TCCR2A, TCCR2B
  const byte Modes [8] [2] = 
  {
  
  { 0,                         0 },            // 0: Normal, top = 0xFF
  { _BV (WGM20),               0 },            // 1: PWM, Phase-correct, top = 0xFF
  {               _BV (WGM21), 0 },            // 2: CTC, top = OCR2A
  { _BV (WGM20) | _BV (WGM21), 0 },            // 3: Fast PWM, top = 0xFF
  { 0,                         _BV (WGM22) },  // 4: Reserved
  { _BV (WGM20),               _BV (WGM22) },  // 5: PWM, Phase-correct, top = OCR2A
  {               _BV (WGM21), _BV (WGM22) },  // 6: Reserved
  { _BV (WGM20) | _BV (WGM21), _BV (WGM22) },  // 7: Fast PWM, top = OCR2A
  
  };  // end of Timer2::Modes
  
  // Activation
  enum { NO_CLOCK, PRESCALE_1, PRESCALE_8, PRESCALE_32, PRESCALE_64, PRESCALE_128, PRESCALE_256, PRESCALE_1024 };
  
  // what ports to toggle on timer fire
  enum { NO_PORT = 0, 
    
    // pin 17, Arduino port: D11
    TOGGLE_A_ON_COMPARE  = _BV (COM2A0), 
    CLEAR_A_ON_COMPARE   = _BV (COM2A1), 
    SET_A_ON_COMPARE     = _BV (COM2A0) | _BV (COM2A1),
    
    // pin 5, Arduino port: D3
    TOGGLE_B_ON_COMPARE  = _BV (COM2B0), 
    CLEAR_B_ON_COMPARE   = _BV (COM2B1), 
    SET_B_ON_COMPARE     = _BV (COM2B0) | _BV (COM2B1),
  };
  
  
  // choose a timer mode, set which clock speed, and which port to toggle
  void setMode (const byte mode, const byte clock, const byte port)
  {
  if (mode < 0 || mode > 7)  // sanity check
    return;
  
  // reset existing flags
  TCCR2A = 0;
  TCCR2B = 0;
  
  TCCR2A |= (Modes [mode] [0]) | port;  
  TCCR2B |= (Modes [mode] [1]) | clock;
  }  // end of Timer2::setMode
  
}  // end of namespace Timer2 

#endif

Credits

abhi gopal

abhi gopal

2 projects • 0 followers

Comments