Blinky's Lab

air-quality

All posts tagged air-quality by Blinky's Lab
  • Posted on

    Archived from radmon.org + more - originally posted 18/09/2017

    I've been working on an air quality monitor over the weekend. I have a working model that samples and logs every 5 minutes into various .csv files that are used for drawing graphs. It is a work in progress, still much to do. Take a look here: https://www.schmoozie.co.uk/airquality/

    Please ignore the data as it is not accurate as is just for testing purposes at the moment. I'll be dumping the collected data and starting from scratch when I get this into an enclosure and mounted properly outside. (All data shown on the page is good.)

    enter image description here

    The hardware is very simple. An ESP8266 (Wemos D1 Mini), I2C LCD driver, 16x2 LCD and a Nova SDS011 PM2.5/PM10 sensor. The software on the ESP8266 simply wakes up the sensor for 20 seconds along with the built in Wi-Fi, then records the last sample, puts the sensor to sleep for 5 minutes and shoots it to my web server where some PHP does the magic and writes the log files etc. I'm pretty happy with it so far.

    This did get properly housed and installed and with a BME280 (temp/humidity/pressure) sensor alongside. However the BME 280 failed not long after installing. Sadly I don't have any pictures of the install yet. Here is a good example of it working on bonfire night 2017: enter image description here

    The Arduino code: (is old so not guaranteed to compile with recent libraries)

    #include <LiquidCrystal_I2C.h>
    #include <ESP8266WiFi.h>
    #include <WiFiClientSecure.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SDS011.h>
    #include <Adafruit_Sensor.h>
    #include <Adafruit_BMP280.h>
    #include <Adafruit_Si7021.h>
    
    // Time to sleep (in milliseconds):
    const int sleepTime = 280000; //280 seconds sleep time
    
    const char* ssid = "Dreamtech";
    const char* password = "lizbo5601";
    const char* host = "172.16.100.100";
    const int httpPort = 80;
    
    float p10, p25;
    int error;
    int countdown;
    bool firstRun = true;
    String str_pm25;
    String str_pm10;
    String str_temp;
    String str_humidity;
    String str_pressure;
    unsigned int stringLength;
    const int greenLED = D0;
    const int redLED = D8;
    const int button = D3;
    bool sampleNow = true;
    unsigned long nextSample;
    int buttonStep = 0;
    int buttonState = 0;
    unsigned long debounce;
    unsigned long hold;
    int menuStep = 1;
    
    bool debug = true;
    bool doSample = true;
    
    SDS011 sds;
    Adafruit_BMP280 bmp; // I2C
    Adafruit_Si7021 sensor = Adafruit_Si7021();
    
    // Set the LCD address to 0x27 for a 16 chars and 2 line display
    LiquidCrystal_I2C lcd(0x27, 16, 2);
    
    void reconnect() {
      if (debug) {
        Serial.print("Connecting to ");
        Serial.println(ssid);
      }
      if (WiFi.status() != WL_CONNECTED) {
        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED) {
          delay(500);
          if (debug) {
            Serial.print(".");
          }
        }
      }
      if (debug) {
        Serial.println("");
        Serial.println("WiFi connected");
        Serial.print("IP address: ");
        Serial.println(WiFi.localIP());
      }
    }
    
    void http_post(String data) {
      if (debug) {
        Serial.print("connecting to ");
        Serial.println(host);
      }
      // Use WiFiClient class to create TCP connections
      WiFiClient client;
      if (!client.connect(host, httpPort)) {
        if (debug) {
          Serial.println("connection failed");
        }
        return;
      }
      String url = "/airquality/postdata.php";
      if (debug) {
        Serial.print("requesting URL: ");
        Serial.println(url);
      }
      String body = String("POST ") + url + " HTTP/1.0\r\n" +
                    "Host: " + host + "\r\n" +
                    "Content-Length: " + data.length() + "\r\n"
                    "Content-Type: application/x-www-form-urlencoded\r\n" +
                    "\r\n" +
                    data;
      if (debug) {
        Serial.print("body: ");
        Serial.println(body);
      }
      client.print(body);
      if (debug) {
        Serial.println("request sent");
      }
      while (client.connected()) {
        String line = client.readStringUntil('\n');
        if (line == "\r") {
          if (debug) {
            Serial.println("headers received");
          }
          break;
        }
      }
      if (debug) {
        Serial.println("closing connection");
      }
    }
    
    void submit_air_conditions(String pm25, String pm10, String temp, String humidity, String pressure) {
      http_post("pm25=" + pm25 + "&pm10=" + pm10 + "&temp=" + temp + "&humidity=" + humidity + "&pressure=" + pressure);
    }
    
    void displayPM (String pm25, String pm10) {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("PM2.5: " + pm25);
      lcd.setCursor(0, 1);
      lcd.print("PM10: " + pm10);
    }
    
    void displayTempHum (String temp, String hum) {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Temp: " + temp + " c");
      lcd.setCursor(0, 1);
      lcd.print("Hum: " + hum + " %");
    }
    
    void displayPres (String pres) {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Pressure: ");
      lcd.setCursor(0, 1);
      lcd.print(pres + " hPa");
    }
    
    void setup() {
      Serial.begin(115200);
      WiFi.mode(WIFI_STA);
      pinMode(greenLED, OUTPUT); //Green LED
      pinMode(redLED, OUTPUT); //Red LED
      pinMode(button, INPUT); //Button
      sds.begin(D6, D7);
      if (!bmp.begin()) {
        if (debug) {
          Serial.println(F("Could not find a valid BMP280 sensor, check wiring!"));
        }
        //while (1);
      }
      sensor.begin();
      lcd.begin();
      lcd.backlight();
      lcd.print("   SDS011 Air");
      lcd.setCursor(0, 1);
      lcd.print("  Quality Meter");
      delay(2000);
    }
    
    void loop() {
      if (digitalRead(button) == HIGH) {
        if (buttonState == 1) {
          hold = 0;
          if (millis() > debounce + 5000) {
            buttonStep = 0;
            if (menuStep != 1) {
              if (debug) {
                Serial.println("Main Display");
              }
            displayPM (str_pm25, str_pm10);
            }
            buttonState = 0;
            menuStep = 1;
          }
        }
      }
      if (digitalRead(button) == LOW) {
        buttonState = 1;
        if (hold <= millis()) {
          if (hold != 0) {
            digitalWrite(redLED, HIGH);
            if (debug) {
              Serial.println("CLEAR!");
              Serial.println("");
            }
            lcd.clear();
            lcd.setCursor(0, 0);
            lcd.print("Reserved");
            lcd.setCursor(0, 1);
            lcd.print("Function");
            hold = millis() + 3000;
            buttonStep = 0;
            menuStep = 1;
            delay(2000);
            if (debug) {
              Serial.println("Main Display");
            }
            displayPM (str_pm25, str_pm10);
            digitalWrite(redLED, LOW);
            return;
          }
        }
        if (debounce < millis()) {
          hold = millis() + 3000;
          if (debug) {
            Serial.println("Button Hold = " + String(hold));
          }
          buttonStep += 1;
          if (buttonStep >= 3) {
            buttonStep = 0;
          }
          if (debug) {
            Serial.println(buttonStep);
          }
        }
        if (buttonStep == 0) {
          if (menuStep == 0) {
            if (debug) {
              Serial.println("Main display");
            }
            displayPM (str_pm25, str_pm10);
            menuStep = 1;
          }
        }
        else if (buttonStep == 1) {
          if (menuStep == 1) {
            if (debug) {
              Serial.println("Temp Hum");
            }
            displayTempHum (str_temp, str_humidity);
            menuStep = 2;
          }
        }
        else if (buttonStep == 2) {
          if (menuStep == 2) {
            if (debug) {
              Serial.println("Pressure");
            }
            displayPres (str_pressure);
            menuStep = 0;
          }
        }
        debounce = millis();
        debounce += 200;
      }
      if (nextSample <= millis()) {
        sampleNow = true;
      }
      if (sampleNow) {
        if (doSample) {
          reconnect();
          sds.wakeup();
          if (firstRun) {
            countdown = 30;
            if (debug) {
              Serial.println("Calibrating SDS011 (first run 30 sec)");
            }
            for (int x = 0; x < 5; x++) {
              digitalWrite(greenLED, HIGH);
              lcd.clear();
              lcd.setCursor(0, 0);
              lcd.print("Starting up");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              digitalWrite(greenLED, LOW);
              delay(1000);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(11, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(12, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(13, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(14, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(15, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
            }
            firstRun = false;
          }
          else {
            countdown = 20;
            if (debug) {
              Serial.println("Calibrating SDS011 (20 sec)");
            }
            for (int x = 0; x < 4; x++) {
              digitalWrite(greenLED, HIGH);
              lcd.clear();
              lcd.setCursor(0, 0);
              lcd.print("Sampling");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              digitalWrite(greenLED, LOW);
              delay(1000);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(8, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(9, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(10, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
              lcd.setCursor(11, 0);
              lcd.print(".");
              lcd.setCursor(0, 1);
              lcd.print(String(countdown) + " ");
              countdown -= 1;
              delay(20);
              digitalWrite(greenLED, LOW);
              delay(980);
              digitalWrite(greenLED, HIGH);
            }
          }
          error = sds.read(&p25, &p10);
          if (!error) {
            str_pm25 = String(p25);
            stringLength = (str_pm25.length());
            str_pm25.remove(stringLength - 1);
            str_pm10 = String(p10);
            stringLength = (str_pm10.length());
            str_pm10.remove(stringLength - 1);
            str_temp = String(sensor.readTemperature(), 2);
            str_humidity = String(sensor.readHumidity(), 2);
            str_pressure = String(bmp.readPressure() / 100);
            if (debug) {
              Serial.println("Air Quality:");
              Serial.println("PM2.5 = " + str_pm25 + " ug/m3");
              Serial.println("PM10 = " + str_pm10 + " ug/m3");
              Serial.println("Temp = " + str_temp + " *c");
              Serial.println("Humidity = " + str_humidity + " %");
              Serial.println("Pressure = " + str_pressure + " hPa");
            }
            submit_air_conditions(str_pm25, str_pm10, str_temp, str_humidity, str_pressure);
            displayPM (str_pm25, str_pm10);
          }
          else {
            if (debug) {
              Serial.println("Error reading sensor");
            }
            lcd.clear();
            lcd.setCursor(0, 0);
            lcd.print("Sensor read");
            lcd.setCursor(0, 1);
            lcd.print("error");
          }
          if (debug) {
            Serial.println("Sleep(" + String(sleepTime) + " milliseconds)\n\n");
          }
          sds.sleep();
          digitalWrite(greenLED, LOW);
        }
        else {
          if (debug) {
            Serial.println("Sampling disabled");
          }
        }
        nextSample = millis() + sleepTime;
        if (debug) {
          Serial.println("Millis: " + String(millis()));
          Serial.println("Next Sample: " + String(nextSample));
        }
        sampleNow = false;
      }
    }