MQ-2 using ESP8266

Here's a sample sketch I cobbled together a minute ago for a demo go/no go situation using an MQ4, an Adafruit Neopixel Ring and a 555 timer attached to a little speaker as an alarm. It worked pretty well for its non life safety demo application. I didn't check it over just now, don't know if it will still compile after you take all the comments out if you're so inclined, but this is what I knocked together for a demo project many moons ago and maybe you'll find it useful. Oh, and TICAT is what I happened to name this thing - it was a 1/10 scale RC crawler with some custom electronics, the likes of which you can discern from the following sketch:

/*******************************************************************************************
********************************************************************************************

version 2.1 : includes an onboard (//optional) temperature sensor.
              contains text strings during warmup to be read at base station.
              color values for alarm values/normal state in final form.
              autoscales and provides gas value in parts per million (ppm) and as %LEL (lower explosive limit)--note this is NOT the same as %v/v (by volume in room air)!
              
version 2.2   software calibrates as an addition to autozero function.
              added double analog read technique to improve reliability of analogRead in multiple instances
              
  Sketch for small gas detector for Arduino Fio (or any 3.3 volt Arduino
  running a 5 volt sensor. The sensor is configured so that the output is
  as a voltage divider, thus the maximum that the sensor pin can output to
  an analog pin is 3.3 volts. There may be some slight deviation in terms of
  the exact concentration of gas, but this is intended to be affixed to a small
  tele operated machine for navigation and medium to longer term sampling and
  monitoring on a more pass/fail basis than a hard measured concentration.

    By hallowed31
  March 14 - May 14, 2015
  Sept 01, 2015:
  Update ver 2.21: Streamlined code, cleaned up old notes within the sketch
                    Changed getMethane() datatype from long to int for cloud logging
                     Changed the way Serial monitor handles data for same
                      Changed startup sequence messaging so it is more logical and helpful
                        Changed the way messages presented (tabbing instead of spacing) to save memory
  RGB color method courtesy of Adafruit Industries.

  Sensor warmup function courtesy of Simon Monk, "The Tab Book of Arduino Projects", 2015 pg326
  NeoPixel functions adapted from Adafruit Industries examples of NeoPixel Hardware.

  "Autoscale" function courtesy of Paul Badger, 2007, Arduino Playground
  
  "Reading Multiple Analog Inputs" double-read technique by Arduino Community Member jondecker76, referenced below.
  
  This software is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************************
*****************************************************************************************/
/*
#include <Adafruit_NeoPixel.h>
#include <avr/power.h>

const int methanePin = A0;
const int timer555 = 4;
const int PIN = 6;        //NeoPixel Pin
const int NUMPIXELS = 16;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

long scaledPPM = 0;
long lel = 0;

int reading = 0; //the raw reading used in main loop and warmup
int d = 0; 
int oldReading = 1023;
long scaledWarmup = 0;

int delayval = 100;       //changed from default 500, used in neoPixel function

void setup() {

  Serial.begin(9600);   //baud rate for the Fio
  pinMode(methanePin, INPUT);
  pinMode(timer555, OUTPUT); //intializing the necessary pins.
  pixels.begin();  //to initialize the NeoPixel ring
  pixels.show(); //initialize all pixels to 'off'
  
  Serial.println(F("\t HAZ3OT: TICAT init."));
  delay(1000);
  Serial.println(F("\t Onboard Methane Detector Active "));
    delay(1000);
  Serial.println(F("\t TICAT system check starting "));
    delay(1000);
  Serial.println(F("\t \t Please Wait..."));
  Serial.println("");
  delay(2000);

  warmup();

  Serial.println("Sensor Ready");
  pixels.show(); //initialize all pixels to 'off'
  delay(delayval);
  setColor(pixels.Color(255, 255, 255)); //white to start 
}

void loop() { 
    
getMethane(); 

//for calibration purposes only
//getVoltage();
     
      /* Alarm Values of Gas Presence negligible amounts, Low Alarm setpoint, Mid Alarm, High Alarm setpoint and
      Normal Condition

      Low Alarm: Gas present but in not immediately dangerous concentration.
      
      MQ-4 datasheet list maximum concentration of 10000 ppm CH4 resolution. The explosive range of methane is commonly stated as 5-15% by volume of air
      or 50,000 to 150,000 parts per million (ppm). 10000 ppm of any gas is 1% v/v (room air by volume). 10,000ppm of methane is 20% if its LEL.
     
      For clarity, if we were talking about room air by volume in parts per million, 5% of 1,000,000 is 50,000 ppm. This would be 100% of the LEL for methane, and any little
      ignition source, such as turning a light switch on or off, between this value and 15% UEL (Upper Explosive Limit), or 150,000ppm would cause all the gas to ignite at once, 
      in other words, an explosion! 
      
      IF WE ARE TRYING THIS SKETCH, AND USING A SENSOR DESIGNED TO MEASURE GAS CONCENTRATION,
       WHETHER FLAMMABLE, CORROSIVE, TOXIC (SUCH AS CO--CARBON MONOXIDE ETC), OR OTHERWISE (ASPHYXIANT SUCH AS CO2--CARBON DIOXIDE,
      IT IS EXTREMELY IMPORTANT THAT WE UNDERSTAND THE VERY IMPORTANT CONCEPTS DESCRIBED HEREIN!
      
      Hardware note: I put a 22 kOhm resistor on mine, and followed the manufacterer's suggested warm up period break in time of over 24 hours.
      
      I want my alarm set points to be based of percentage of lower explosive limit (by volume of normal room air at room temperature and low relative humidity)
      where:
            10,000 ppm maximum on the sensor means it will detect methane concentration up to 1% of room air by volume. This is still only approximately 20% of the LEL for methane,
            often published as 5% (to 15% for the UEL, or Upper Explosive Limit, the concentration of methane in room air above which will be too rich to burn).
            Since the explosive range for methane is 5-15% room air by volume, (and I am only really interested in the LEL or 5%, at 50,000ppm),
            I can set my Low Alarm value at 1% of the LEL (not of room air by volume, this is 1% of 5% room air by volume(50,000)so 500 ppm up to the Middle Alarm setpoint).
            Middle Alarm is 5% of the LEL (5% of 50,000 ppm, or 2500 ppm, to the High Alarm value setpoint).
            High Alarm is 10% of the LEL at 5000 ppm, to the maximum sensor resolution of 10,000 ppm (1% v/v; this is 20% of the LEL for methane...I know I am beating this concept 
            to death in this text; however, the importance of understanding this cannot be overstated!)
            The values are intentionally set to provide a massive safety factor, especially in an enclosed area, just because the gas value is x at one location, does not mean
            it it x at another location. Since the vapour density of methane is 0.65 as compared to 1.0 for normal air, it will rise. Thus, the gas value could be x at one location
            on a given horizontal plane in an enclosed area, and x at another point on the same horizontal plane, but easily (and likely) be much higher at any point on the 
            vertical plane of the enclosed space. The higher the ceiling, and the more time the gas has been accumlating (which is quite likely an unknown), the more drastic the difference 
            between the gas level at the lower and higher levels.
      */
 /*     
  if (scaledPPM <100)
  {
     digitalWrite(timer555, LOW);
    setColor(pixels.Color(255, 255, 255)); // White, like a headlight
     Serial.println(F("I smell nothing..."));
  }
    
//500ppm is 1% of the LEL for methane
 else if (scaledPPM >= 100 && scaledPPM < 500)
  {
    digitalWrite(timer555, LOW); //no audio
    theaterChase(pixels.Color(0, 255, 0), 10); // Green blinking, something detected but far below any LEL
  }
  
//2500ppm is 5% of the LEL for methane
  else if (scaledPPM >= 500 && scaledPPM < 2500)
  {
    digitalWrite(timer555, LOW);
    theaterChase(pixels.Color(0, 255, 0), 10); //Green blinking, with audible warning 
     Serial.println(F("Low Alarm, continue monitoring... ")); 
  }
  
//5000ppm is 10% of the LEL for methane
  else if (scaledPPM >= 2500 && scaledPPM < 5000)
  {
    digitalWrite (timer555, HIGH);
    theaterChase(pixels.Color(255, 255, 0), 10); //Yellow Blinking, with sound
     Serial.println(F("CAUTION ADVISED, MIDDLE ALARM! "));
  }
  
//10000ppm is 20% of the LEL for methane
  else if (scaledPPM >= 5000 && scaledPPM <= 9900)
  {
    digitalWrite(timer555, HIGH);
    theaterChase(pixels.Color(255, 0, 0), 10); //Red Blinking
     Serial.println(F("             HIGH ALARM!!! "));
  }
  
  else if (scaledPPM > 9900)
  {
    digitalWrite(timer555, HIGH);
    theaterChase(pixels.Color(0, 0, 127), 10); //  Blue, as OverRange
    Serial.println(F("           SENSOR OVER RANGE! Gas May Be Over 20% LEL!!!!"));
    
  } 
}

/****************************************************************************************************************************
*****************************************************************************************************************************
                                              THEATRE CHASE NEOPIXEL FUNCTION
*****************************************************************************************************************************
****************************************************************************************************************************/

//Theatre-style crawling lights.
/*
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j = 0; j < 10; j++) { //do 10 cycles of chasing
    for (int q = 0; q < 3; q++) {
      for (int i = 0; i < pixels.numPixels(); i = i + 3) {
        pixels.setPixelColor(i + q, c);  //turn every third pixel on
      }
      pixels.show();

      delay(wait);

      for (int i = 0; i < pixels.numPixels(); i = i + 3) {
        pixels.setPixelColor(i + q, 0);      //turn every third pixel off
      }
    }
  }
}

/****************************************************************************************************************************
*****************************************************************************************************************************
                                                MQ-x GAS SENSOR FUNCTION
*****************************************************************************************************************************
****************************************************************************************************************************/
/*
  
int getMethane(){  //Sept 01, 2015 changed datatype from long to int. If there is a problem, change this back first

  // read from the sensor:
  //if values after autoscaling seem off, run example sketch "Analog In Out Serial"
  //or uncomment out getVoltage() function in loop and reupload
  
  reading = analogRead(methanePin); 
  //and read it again, after a short delay, discarding first read (source: http://forum.arduino.cc/index.php?topic=54976.0 )
  delay(10);
  reading = analogRead(methanePin); //this should help to stabilize by reading this set after the multiplexer is already switched
  delay(10);                                    //and allowing the voltage to steady up after the switching.
   
   scaledPPM = autoScale(190, 1023, 0, 10000, reading); //TO DO: FIRST ARGUMENT NEEDS TO MATCH THE RAW analogRead OF YOUR WARMED UP SENSOR
   
    lel = scaledPPM / 500; 
     Serial.print("ppm: ");
     Serial.print(scaledPPM);
     Serial.print(" % LEL: ");
     Serial.println(lel);
     Serial.println("");
                               
 // delay(2);        // delay in between reads for stability !!!!!!NOTE SEPT 01 2015--IS THIS NEEDED? SEEMS REDUNDANT
}

/****************************************************************************************************************************
*****************************************************************************************************************************
                                                  WARMUP FUNCTION
*****************************************************************************************************************************
****************************************************************************************************************************/
/*
void warmup()   //allows heaters to warm up respective gas sensors to working temperatures. Readings will fall, then slightly rise again Monk, pg326

{
  Serial.println(F("\t ...Checking Alarm Function"));
  Serial.println(F("\t \t Listen for Alarm Tone"));
   delay(1000);
  digitalWrite(timer555, HIGH);

  bitRead(PORTD, 4); //Reads bit 3 of register PORTD which contains the current state (high/low) of pin 3.
  if (PORTD, 4 != 0) Serial.println(F("Alarm Function OK"));

  else if (PORTD, 4 == 0) Serial.println(F("Alarm Malfunction")); //reads current state of pin 4, register D, logical 1 or 0
                                                                  //so if logical 0 here, will read it out
  delay(2000);

  digitalWrite(timer555, LOW);

  delay(2000);
  Serial.println(F("\t Gas Advisory at 100 ppm; 0.01% v/v"));
  delay(2000);
  Serial.println(F("\t Low Alarm 500 ppm; 0.1% v/v; 1% to 5% LEL CH4"));  //gas value in ppm /10000 = % v/v
  delay(2000);
  Serial.println(F("\t Mid Alarm 2500 ppm; 0.25% v/v; 5% to 10% LEL CH4"));
  delay(2000);
  Serial.println(F("\t High Alarm 5000 ppm; 0.5% v/gv; 10% to 20% LEL CH4")); //based off of widely published value of LEL CH4 at 50,000ppm, or 5% v/v in room air.
  delay(2000);
  Serial.println(F("\t Blue light indicates sensor Over Range( >10,000ppm)"));
  delay(1000);
  Serial.println(F("\t  MAY BE IN EXCESS OF 20% LEL CH4! "));
  delay(2000);
  Serial.println(F("\t \t Check Indicator LEDs are ON..."));


  while (millis() < 240000 ) {     //this will be determined by sensor datasheet, this is 240,000 milliseconds, or four minutes

    colorWipe(pixels.Color(0, 0, 190), 50); // Blue
    colorWipe(pixels.Color(127, 127, 127), 50); //White
    colorWipe(pixels.Color(190, 0, 0), 50); // Red
    colorWipe(pixels.Color(190, 190, 0), 50); //Yellow
    colorWipe(pixels.Color(0, 190, 0), 50); // Green
    /* all the colors of the various alarm setpoints/indicators
       white for normal condition; blue for overrange condition, ie greater than 10,000 ppm on the sensor;
       green for slight gas concentrations; yellow for rising gas concentrations; red for gas detected. 
    */
/*
    digitalWrite(timer555, LOW);
    reading = analogRead(methanePin);
    //and read it again, after a short delay, discarding first read (source: http://forum.arduino.cc/index.php?topic=54976.0 )
    delay(10);
    reading = analogRead(methanePin); //this should help to stabilize by reading this set after the multiplexer is already switched
                                        //and allowing the voltage to steady up after the switching.
    d = reading - oldReading;
    oldReading = reading;
    delay(10);
    
    scaledWarmup = autoScale(190, 1023, 0, 10000, reading);  //TO DO: FIRST ARGUMENT NEEDS TO MATCH THE RAW ANALOGREAD OF YOUR WARMED UP SENSOR
       Serial.print(scaledWarmup);
    Serial.println(" ppm");
    if (d > 9) {
      digitalWrite(timer555, HIGH);               // If there are a lot of blips at startup, an increased warmup time is recommended
      Serial.print(F("           ERROR CHECK "));        //a nicely warmed sensor should not beep and blip a lot
      Serial.print(          d);

      delayMicroseconds(10000);                  // if it continues after a second warmup, and we have controlled for temperature and humidity
    }                                            // then remove sensor from service and check the hardware.
    // recall here that a value displayed "d" is the difference between subsequent readings,
    // not the actual reading
    else {
      digitalWrite(timer555, LOW);
      Serial.println(F("\t Sensor Warming"));
      Serial.println(F("\t ...Please Wait"));

    }
  }
}



/****************************************************************************************************************************
*****************************************************************************************************************************
                                                AUTOSCALE FUNCTION
*****************************************************************************************************************************
****************************************************************************************************************************/
/*
int autoScale( int originalMin, int originalMax, int newBegin, int
               newEnd, int inputValue) {

  long zeroRefOriginalMax = 0;
  long zeroRefnewEnd = 0;
  long zeroRefCurVal = 0;
  long rangedValue = 0;
  boolean invFlag = 0;

  // Check for out of range inputValues
  if (inputValue < originalMin) {
    inputValue = originalMin;
  }
  if (inputValue > originalMax) {
    inputValue = originalMax;
  }

  // Zero Reference the values
  zeroRefOriginalMax = originalMax - originalMin;

  if (newEnd > newBegin) {
    zeroRefnewEnd = newEnd - newBegin;
  }
  else
  {
    zeroRefnewEnd = newBegin - newEnd;
    invFlag = 1;
  }

  zeroRefCurVal = inputValue - originalMin;


  // Check for originalMin > originalMax  - the math for all other cases i.e. negative numbers seems to work out fine
  if (originalMin > originalMax ) {
    return 0;
  }
  if (invFlag == 0) {
    rangedValue =  ((zeroRefCurVal * zeroRefnewEnd) /
                    zeroRefOriginalMax) + newBegin ;
  }
  else     // invert the ranges
  {
    rangedValue =  newBegin - ((zeroRefCurVal * zeroRefnewEnd) /
                               zeroRefOriginalMax)  ;
  }

  return rangedValue;
}

/****************************************************************************************************************************
*****************************************************************************************************************************
                                              SET COLOR FUNCTION
*****************************************************************************************************************************
****************************************************************************************************************************/
/*
void setColor(int i) {
  for (int i = 0; i < NUMPIXELS; i++) {

    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(255, 255, 255)); //white

    pixels.show(); // This sends the updated pixel color to the hardware.

    delay(delayval); // Delay for a period of time (in milliseconds).

  }
}

/****************************************************************************************************************************
*****************************************************************************************************************************
                                          COLOR WIPE FUNCTION
*****************************************************************************************************************************
****************************************************************************************************************************/
/*
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for (uint16_t i = 0; i < pixels.numPixels(); i++) {
    pixels.setPixelColor(i, c);
    pixels.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if (WheelPos < 170) {
    WheelPos -= 85;
    return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
    WheelPos -= 170;
    return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}
/********************************************************************************************************************************
*********************************************************************************************************************************
                                        GET RAW VOLTAGE FUNCTION
*********************************************************************************************************************************
********************************************************************************************************************************/
 //Simply uncomment out the getVoltage() function in the main loop 
 //to see the voltage being input on the analog line. This is to assist 
 //with calibration, where necessary
/*
 float getVoltage() {
   float voltage = analogRead(methanePin) * 3.3 /1024.0;           
   Serial.println(voltage); 
   }
*/