Project : Measure air quality

The purpose is to build a system which can measure some elements of air quality

Requirements

  1. Measure dust
  2. Measure temperature
  3. Measure humidity
  4. Measure gases/smoke
  5. Report realtime measurement on a local display
  6. Upload data to an external site, where data can be shown in graphs
  7. Log data locally, so the program can collect data without an internet connection.
  8. LEDs to indicate a healthy operation (green) or errors (red)
  9. Show data in local LCD

Due to requirements to keep it simple to put together and code, I chose to do this on the Grove platform, which sits on top of a Raspberry Pi and has a wide range of sensors, which plugs straight in. Further more most of the sensors has a RPi library making coding much simpler.

 

Resources

Links to sources of information for the components needed beside the RPi:
Grove Pi start kit 
Grove Pi Dust sensor 
Grove Pi Multichannel gas sensor  (does not have a RPi library, so I will deal with this last)
Grove Pi MQ2 gas sensor
Grove Pi light sensor
Grove Pi sound sensor
Grove LCD display

 

Preparation of Raspberry Pi & Grove Pi

***as of 13th of April 2020***

  1. Use a standard installation of the RPi and update/upgrade.
  2. Install the Grove Pi software using the guide 
  3. Upgrade firmware on the Grove Pi
  4. Install the libraries needed for uploading data to Thingspeak:
    1. Paho MQTT
      sudo pip3 install paho-mqtt
    2. Psutil
      sudo pip3 install psutil
  5. Connect sensors to ports as mentioned in the code or as you see fit, just remember to connect digital to digital, analog to analog and that the Dust sensor has to be connected to D2

 

Solution to upload data

Thingspeak  will be used to upload data to. I did search for examples, but most used httplib and urllib, which has changed with Python3, so the examples didn’t work.
So I decided on using another method.Example used for uploading data 

 

Current build and requirements developed (v8)

Sensors:

  1. Temperature
  2. Humidity
  3. Dust
  4. light
  5. Sound
  6. Gas/smoke

Requirements:

  1. Present data on LCD
  2. Upload data to Thingspeak
  3. Send email warnings
  4. Log data in a file

Code is listed below

 

Issues noted and worked around

  1. For some reason the data from the light sensor and sound sensor has to be collected before the Temperature & Humidity sensor, otherwise the data is invalid and the same.
  2. Most examples of uploading data to Thingspeak use libraries not working with Python 3
  3. There were mismatches between what sensors were supported on the RPi and which were not in the Seeedstudio documentation. Check online and on Dexterindustries website & Github

Live data from a sensor

#######################################importing libraries####################################
from __future__ import print_function
import paho.mqtt.publish as publish
import psutil
import grovepi
import string
import random
import time
import atexit
from grove_rgb_lcd import *
from grovepi import *
import smtplib
import logging
 
#######################################Setting up sensors and values############################
atexit.register(grovepi.dust_sensor_dis)
HTsensor = 7 #D7
light_sensor = 1 #A1
SoundSensor = 0 #A0
gas_sensor = 2 #A2
LED_green = 4 #D4
LED_red = 3 #D3
pinMode(LED_green,"OUTPUT")
pinMode(LED_red,"OUTPUT")
grovepi.dust_sensor_en()
grovepi.pinMode(gas_sensor,"INPUT")
SMTP_SERVER = 'smtp.gmail.com' #Email Server (don't change!)
SMTP_PORT = 587 #Server Port (don't change!)
GMAIL_USERNAME = 'XXXXXXXXX@gmail.com' #change this to match your gmail account
GMAIL_PASSWORD = 'XXXXXXXXX'  #change this to match your gmail password
MaxDust=5000 # Max level of dust.  If levels above is registered a warning email is sent
emailsend=0
logging.basicConfig(filename='airquality.log',format='%(asctime)s %(message)s',datefmt='%d-%m-%Y %H:%M:%S',level=logging.DEBUG)

#############################Setting up data to write data to Thingspeak########################
string.alphanum='XXXXXXXXX'#insert yours
channelID = "XXXXX" #insert yours
writeAPIKey = "XXXXXXXXX" #insert yours
mqttHost = "mqtt.thingspeak.com"
mqttUsername = "XXXXXXXXX" #insert yours
mqttAPIKey ="XXXXXXXXX" #insert yours
tTransport = "websockets"
tPort = 80
topic = "channels/" + channelID + "/publish/" + writeAPIKey

#############################Setting up email class############################################
class Emailer:
    def sendmail(self, recipient, subject, content):
         
        #Create Headers
        headers = ["From: " + GMAIL_USERNAME, "Subject: " + subject, "To: " + recipient,
                   "MIME-Version: 1.0", "Content-Type: text/html"]
        headers = "\r\n".join(headers)
 
        #Connect to Gmail Server
        session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        session.ehlo()
        session.starttls()
        session.ehlo()
 
        #Login to Gmail
        session.login(GMAIL_USERNAME, GMAIL_PASSWORD)
 
        #Send Email & Exit
        session.sendmail(GMAIL_USERNAME, recipient, headers + "\r\n\r\n" + content)
        session.quit
 
sender = Emailer()
sendTo = 'XXXXX@gmail.com' #email to get warnings
emailSubject = "Hello World"
emailContent = "This is a test of my Emailer Class"

#####################################Program start#############################################
while True:
    try:
        #Set green operation
        digitalWrite(LED_green,1)
        digitalWrite(LED_red,0)
        
 
        #Gather and convert data from sensors
        sound_level = grovepi.analogRead(SoundSensor)
        light_intensity = grovepi.analogRead(light_sensor)
        sensor_value = grovepi.analogRead(gas_sensor)
        density = (float)(sensor_value / 1024.0)# Calculate gas density - large value means more dense gas
        density = round(density,3) #Rounding a very long number to something short
        [new_val,other,lowpulseoccupancy] = grovepi.dust_sensor_read()
        lowpulseoccupancy = round(lowpulseoccupancy,1)#Rounding a very long number to something short
        [temp,hum] = grovepi.dht(HTsensor,0)
 
        #Print data to concole
        print ("Light =" , light_intensity)
        print ("Sound =", sound_level )
        print ("Gas sensor data: value =", sensor_value, " Gas density =", density)
        print ("Dust sensor data : new_val=" , new_val, " other=", other, " lowpulseoccupancy=",lowpulseoccupancy)
        print ("Temperature =", temp, "C")
        print ("Humidity =", hum,"%")
        print ("Light =",light_intensity)
        print ("Sound =",sound_level)
        
        #Prep connection to Thingspeak
        clientID=''
 
        # Create a random clientID.
        for x in range(1,16):
            clientID+=random.choice(string.alphanum)

        #Prepare data for use on LCD & Thingspeak
        t = str(temp)
        h = str(hum)
        d = str(lowpulseoccupancy)
        Md = str(MaxDust)
        l = str(light_intensity)
        s = str(sound_level)
        g = str(density)
 
        #To avoid posting empty data as "0" from the dust sensor. New_val means there is new data
        if new_val:
            print ("**New data from the dustsensor - publish to Thingspeak & send email**")
       
            #build the payload string for Thingspeak
            payload = "field1=" + t + "&field2=" + h + "&field3=" + d + "&field4=" + l + "&field5=" + s + "&field6=" + g
 
            # attempt to publish data to the topic on Teamspeak.
            try:
                publish.single(topic, payload, hostname=mqttHost, transport=tTransport, port=tPort,auth={'username':mqttUsername,'password':mqttAPIKey})
                print ("Published to Teamspeak to host:" ,mqttHost , " clientID=" ,clientID)
 
            except (KeyboardInterrupt):
                break
            except:
                print ("There was an error while publishing the data.")
                digitalWrite(LED_green,0)
                digitalWrite(LED_red,1)
 
            #Sending emails
            if (emailsend==0 and lowpulseoccupancy>MaxDust):
                emailSubject = "Dust level is above limit"
                emailContent = "At " + time.ctime() + " dust was registered at " + d + " with a warning level of " + Md
                sender.sendmail(sendTo, emailSubject, emailContent) 
                print ("Warning eMail has been sent")
                emailsend=1
        
            elif (emailsend==1 and lowpulseoccupancy<=MaxDust):
                emailSubject = "Dust level has dropped below limit"
                emailContent = "At " + time.ctime() + " dust was registered at " + d + " with a warning level of " + Md
                sender.sendmail(sendTo, emailSubject, emailContent) 
                print ("Email stating dust is below max has been sent")
                emailsend=0
        
            elif (emailsend==1 and lowpulseoccupancy>MaxDust):
                print ("Dust above MAX, but email has already been sent...")
        
            else:
                print ("Dust was registered at " + d + " with a warning level of " + Md + ". No need for a warning email")
                emailsend=0
            
            #logging data
            log_entry = "Temp=" + t + " & Humidity=" + h + " & Dust=" + d + " & Light=" + l + " & Sound=" + s + " & Gas=" + g
            logging.info(log_entry)
            
            #Print on LCD
            setText("T:" + t + "C" + " H:" + h + "%" + " Dust:" + d + "psc/L")

            
        #Set LCD color for day and night time, so day is Blue and night is dark Red
        if (light_intensity>120):
            print ("Daytime")
            setRGB(0,0,255)
        else:
            print ("Nighttime")
            setRGB(100,0,0)

        print ("**************************************************************")
        #Wait 60 seconds
        time.sleep(60)
        
    except (IOError,TypeError) as e:
        print("Error")
        digitalWrite(LED_green,0)
        digitalWrite(LED_red,1)
#################################Program end###################################################