Hazardous Gas Monitor

Pages
Contributors: jenfoxbot
Favorited Favorite 17

Program It

First, set up a data stream on the data.sparkfun.com service. Next, write a program to read in the analog value of each gas sensor, convert it to PPM, and check it against known safe thresholds. Based on OSHA safety standards, the thresholds for the three gases are as follows:

  • LPG: 1,000 PPM
  • Methane: 1,000 PPM
  • CO: 50 PPM

If you want to get up and running quickly, or are new to programming, feel free to grab the code from below, or you can get the most up to date files from the GitHub repository. Use it as-is or modify to suit your particular needs.

language:c
// This #include statement was automatically added by the Particle IDE.
//This library is used to push data to the data.sparkfun.com server.
#include "SparkFunPhant/SparkFunPhant.h"
#include "math.h" 

//This code was written by Jennifer Fox <jenfoxbot@gmail.com>
/*
 * ----------------------------------------------------------------------------
 * "THE Coffee-WARE LICENSE" (Revision 42):
 * <jenfoxbot@gmail.com>  wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a coffee in return.
 * ----------------------------------------------------------------------------
 */

//Variables to push data to data.sparkfun.com host -- Change publicKey[] and privateKey[]
const char server[] = "data.sparkfun.com"; // Phant destination server
const char publicKey[] = "INSERT_PUBLIC_KEY_HERE"; // Phant public key
const char privateKey[] = "INSERT_PRIVATE_KEY_HERE"; // Phant private key

Phant phant(server, publicKey, privateKey); // Create a Phant object

const unsigned long postingRate = 20000; //Post rate to data.sparkfun.com (time in milliseconds)
unsigned long lastPost = millis(); //Keeps track of posting rate


//Define analog pins on Photon to use for sensors
const int LPG = A0;
const int NG = A1;
const int CO = A2;

//Define digital pins on Photon to use for LEDs,  buzzer, and MQ7 (CO sensor) heater
const int LPGled = D0;
const int NGled = D1;
const int COled = D2;
const int buzzer = D3;
const int CORelayPin = D6;
const int COVoltPin = D7;

//Set up raw signal and PPM variables for each gas sensor
int LPGRaw;
int NGRaw;
int CORaw;

int LPGppm;
int NGppm;
int COppm;

//Set safety threshold levels for each  hazardous gas
const int  LPGthresh = 1000;
const int NGthresh = 1000;
const int COthresh = 50;


//Set variables for CO sensor (MQ7) voltage cycle
unsigned long startMillis;        
unsigned long switchTimeMillis;
const int CO_5V_Interval = 60000; //60s for 5V interval
const int CO_1_5V_Interval = 90000; //90s for 1.5V interval
bool heaterInHighPhase;

void setup() {
    Serial.begin(9600);

    //Initialize LED and buzzer output pins
    pinMode(LPGled, OUTPUT);
    pinMode(NGled, OUTPUT);
    pinMode(COled, OUTPUT);
    pinMode(buzzer, OUTPUT);

    //Initialize CO sensor heater pins
    pinMode(CORelayPin, OUTPUT);
    pinMode(COVoltPin, OUTPUT);

    //Set start time (for CO sensor heater voltage)
    startMillis = millis();
    turnHeaterHigh();
}

void loop() {
    //Cycle CO sensor (MQ7) heater voltage

    if(heaterInHighPhase){
    // 5V phase - check to switch
        if(millis() > switchTimeMillis) {
        turnHeaterLow();
        }
    }
    else {
    // 1.4V phase - check to switch
        if(millis() > switchTimeMillis) {
        turnHeaterHigh();
        }
    }


    //Read in analog value from each gas sensor -- use function defined below to measure CO sensor at end of voltage cycle
    LPGRaw = analogRead(LPG); 
    NGRaw = analogRead(NG);
    CORaw = measureCOSensor();

    //Caclulate the PPM of each gas sensor using the funtions defined below            
    LPGppm = LPG_ppm(LPGRaw); 
    NGppm = NG_ppm(NGRaw); 
    COppm = CO_ppm(CORaw);

    //Serial monitor print for debugging and checking data 
    Serial.println(NGRaw);
    Serial.println(NGppm);
    delay(1000);

    //Check gas sensor measurements against safety thresholds
    checkThreshold(LPGppm, NGppm, COppm);

    //Wait to post until ~ 20s has lapsed
    if (lastPost + postingRate < millis()) {  
        Serial.println("Reading!");

        postToPhant(LPGppm, NGppm, COppm); //Post gas sensor readings and unit (PPM) to your data stream at data.sparkfun.com

        lastPost = millis();
    }

}

//Functions to calculate PPM from Photon analog reading
//Each equation is determined by visually picking points, plotting PPM v. V_RL, then fitting a trendline to the curve (exponential)
//Calculate LPG PPM
int LPG_ppm(double rawValue){

    double ppm = 26.572*exp(1.2894*(rawValue*3.3/4095)); //Multiply raw analog value by 3.3/4095 to convert to a voltage
    return ppm;
}

//Calculate NG PPM
int NG_ppm(double rawValue){

    double ppm = 10.938*exp(1.7742*(rawValue*3.3/4095)); 
    return ppm;
}

//Calculate CO PPM
int CO_ppm(double rawValue){

    double ppm = 3.027*exp(1.0698*(rawValue*3.3/4095));
    return ppm;
}


//Function to check PPM reading with maximum safe PPM threshold
//Include a margin of error (currently 10%)
void checkThreshold(int lpgppm, int ngppm, int coppm){
    int led1;
    int led2;
    int led3;

    if (lpgppm >= LPGthresh*0.9){
        digitalWrite(LPGled, HIGH);
        led1 = TRUE;
    } 
    else{
        digitalWrite(LPGled, LOW);
        led1 = FALSE;
    }

    if (ngppm >= NGthresh*0.9){
        digitalWrite(D1, HIGH);
        led2 = TRUE;
    } 
    else{
        digitalWrite(NGled, LOW);
        led2 = FALSE;
    }

    if (coppm >= COthresh*0.9){
        digitalWrite(D2, HIGH);
        led3 = TRUE;
    } 
      else{
        digitalWrite(COled, LOW);
        led3 = FALSE;
    }

    if(led1 | led2 | led3){
        digitalWrite(buzzer, HIGH);
    }

    else{digitalWrite(buzzer, LOW);}


}

//Functions to switch heater voltage on MQ7 (CO) sensor
void turnHeaterHigh(){
  // 5v phase
  digitalWrite(COVoltPin, LOW);
  digitalWrite(CORelayPin, HIGH);

  heaterInHighPhase = true;
  switchTimeMillis = millis() + CO_5V_Interval;
}

void turnHeaterLow(){
  // 1.4v phase
  digitalWrite(COVoltPin, HIGH);
  digitalWrite(CORelayPin, LOW);

  heaterInHighPhase = false;
  switchTimeMillis = millis() + CO_1_5V_Interval;
}

//Function to read CO sensor voltage (just before switching to 1.5V)
int measureCOSensor(){
  unsigned int gasLevel = analogRead(CO);
  unsigned int time = (millis() - startMillis) / 1000;
  delay(time);

  return gasLevel;
}



//Function to post data to data.sparkfun.com host
//Many thanks to Jim Lindblom <jim@sparkfun.com> for the sample code and Phant library.
int postToPhant(int lpg, int ng, int co){

    phant.add("lpg", lpg); //Data stream field name "sensorvalue1"
    phant.add("ng", ng); //Data stream field name "sensorvalue2"
    phant.add("co", co); //Data stream field name "sensorvalue3"

    TCPClient client;
    char response[512];
    int i = 0;
    int retVal = 0;

    if (client.connect(server, 80)) // Connect to the server
    {
        // Post message to indicate connect success
        Serial.println("Posting!"); 

        // phant.post() will return a string formatted as an HTTP POST.
        // It'll include all of the field/data values we added before.
        // Use client.print() to send that string to the server.
        client.print(phant.post());
        delay(1000);
        // Now we'll do some simple checking to see what (if any) response
        // the server gives us.
        while (client.available())
        {
            char c = client.read();
            Serial.print(c);    // Print the response for debugging help.
            if (i < 512)
                response[i++] = c; // Add character to response string
        }
        // Search the response string for "200 OK", if that's found the post
        // succeeded.
        if (strstr(response, "200 OK"))
        {
            Serial.println("Post success!");
            retVal = 1;
        }
        else if (strstr(response, "400 Bad Request"))
        {   // "400 Bad Request" means the Phant POST was formatted incorrectly.
            // This most commonly ocurrs because a field is either missing,
            // duplicated, or misspelled.
            Serial.println("Bad request");
            retVal = -1;
        }
        else
        {
            // Otherwise we got a response we weren't looking for.
            retVal = -2;
        }
    }
    else
    {   // If the connection failed, print a message:
        Serial.println("connection failed");
        retVal = -3;
    }
    client.stop();  // Close the connection to server.
    return retVal;  // Return error (or success) code.
}

Change the following in the code:

  1. Copy and paste your data stream public key to the array called publicKey[].

    const char publicKey[] = "INSERT_PUBLIC_KEY_HERE";

2.Copy and paste your data stream private key to the array called privateKey[].

`const char privateKey[] = "INSERT_PRIVATE_KEY_HERE";`

To monitor the Photon output, use the Particle driver downloaded as described in the "Connecting Your Device" Photon tutorial. Once this is installed, in the command prompt, type particle serial monitor. This is super helpful for debugging and checking that the Photon is posting data to the web.