Blinky's Lab

Logging

All posts tagged Logging 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;
      }
    }
    
    
  • Posted on

    Archived from radmon.org - originally posted 20/03/2016

    After some more testing and investigation I found the issue in sketchy GPS was actually the code. I've fixed it and the results are much, much better. There is just one tiny issue now where the CPM being read from the counter is recorded occasionally with an extra digit or two counts on one record. I reckon once that is sorted I can call it version 1.0 :)

    I have updated the download link with the latest code. Here are the results so far:

    https://www.google.com/maps/d/edit?mid=zasLAdbQXqtw.kInoUYbBsJO8&usp=sharing

    enter image description here

    I changed the way the serial CPM is read and that appears to have fixed the issue with the serial data not being read properly..

    I have uploaded the changes to the zip file and can be found here.

    It does very occasionally miss the first digit from the CPM but knowing what I know now about why this is happening it would require a complete rewrite of the code. The issue is caused by the timing from the CPM being transmitted by the GC10 and then the serial is being read by the microprocessor. If the CPM is transmitted from the GC10 before the microprocessor is ready to read it it missed off the first digit. I left it logging all night and I found this happened 3 times in 10,000 records. So whilst not 100% perfect I feel this is 99.9% perfect and is certainly decent enough for my requirements.

    I'm happy to call this version 1.0 :cheer:

  • Posted on

    Archived from radmon.org - originally posted 19/03/2016

    I have sorted the battery issue by adding a couple of 18650 Lipo's and a buck converter to regulate the voltage to 5.0V. That seems to be holding up well. The entire unit (logger and counter) consume about 10ma at 20CPM background radiation so roughly working out if my batteries are 2000mAh (they state 2600mAh but I doubt I will get that) and the unit uses 10mA then it should last for around 200 hours! I just need to sort a charging circuit now to charge the batteries when the unit is powered from 12v from the car, or mains adapter.

    I went out for a quick test with it and it seems to be OK except for sketchy GPS data. I think I need to improve the GPS antenna, so adding an external antenna should sort that. I have a Peugeot car with heat reflective windscreen and it plays havok with most cheaper GPS receivers. At least I'm hoping that is the issue!

    Here is the data for anyone interested and a quick picture of what it looks like so far: https://www.google.com/maps/d/viewer?mid=zasLAdbQXqtw.kxVyakPPw-s0

    enter image description here

  • Posted on

    Archived from radmon.org - originally posted 17/03/2016

    I recently bought myself a DP-66M from ebay, then another one, then a NetIO GC-10 and I have another on the way. Maybe a different one soon? It's fair to say I have been bitten by the Geiger counter bug!

    I am building a static outdoor monitoring station at home, monitoring 24/7, but currently waiting on parts from China. In the meantime I have been playing with the GC-10 and built a prototype mobile GPS logging unit to take on my travels. I live not too far from Heysham power station and the Westinghouse Springfields nuclear fuel manufacturing site, so I will be having a drive around them soon to see if there are any elevated levels

    enter image description here

    The unit uses an Arduino Pro-mini at the core, with a GY-GPS6MV2 GPS receiver module and a Catalex SD card module. All are cheap as chips on ebay from China. The Arduino takes the GPS data via RS232, the CPM from the GC-10 RS232, does a little processing and parsing of the data and shoves it on the SD card in a CSV. Each time the logging is started it creates a new filename generated from the date/time so should be easy to get the data and upload it to Google maps or something similar (I haven't gotten round to that part yet). There is a bi-coloured LED that changes from red to green when a good satellite lock is acheived and it will not start to log the data on the card until a valid date/time is received from GPS. This keeps the CSV files nice and tidy with proper logs only.

    In testing I have had issues with the batteries (2x AA via a 5v boost converter to the GC-10), but I think that was down to bad batteries, so I am currently testing on fresh batteries. I checked the current and it is pulling just under 4mA at the batteries, so whilst they won't last long, it should be enough for some walky-walky monitoring. I plan to add an external GPS antenna/socket and maybe a couple of 18650 Lipo batteries and charger/protection circuit/module. I think it would be nice to be able to power from either the car 12v or maybe a 5v USB type power input that would charge the batteries when plugged in, but that is for another day.

    enter image description here

    A zip file containing the source/schematic etc. can be download here: GPS Geiger Counter. Please note this was a long time ago and may contain old library code, so may not compile as is.

    Contained in the zip are some photos of my prototype build, the Arduino source and schematic so anyone that wants can go build one also! Some knowledge of electronics/arduino will probably be needed as I have not, nor intend on doing a step-by-step.

    ETA: I forgot to mention that this can be used with any Geiger counter that spits out the CPM over RS232, such as the counters to connect to Radlog.

    AETA: I also forgot to mention that some of the code is a little clumsy and probably not very efficient, especially the LED part for changing the colour etc, but that said it does seem to run well on the Pro-mini.