IoTWarriorsPranav ChokdaKamaljit Chahal
Published © GPL3+

Scalable Intelligent Air Quality Monitoring and Response

Air Quality Monitoring that uses the Intel Edison Compute Module, Amazon AWS, Visualization through Kibana, and drones!

AdvancedFull instructions providedOver 1 day6,828
Scalable Intelligent Air Quality Monitoring and Response

Things used in this project

Hardware components

Intel Arduino expansion board
×1
Seeed Studio Grove base shield V2
×1
Seeed Studio Gas Sensor (MQ2)
×1
Seeed Studio Gas Sensor (MQ5)
×1
Seeed Studio Gas Sensor (MQ9)
×1
Seeed Studio Grove GPS
×1
Solo
3DR Solo
×1
9V battery (generic)
9V battery (generic)
×2
9V Battery Clip
9V Battery Clip
×1

Software apps and online services

AWS IoT
Amazon Web Services AWS IoT
AWS SNS
Amazon Web Services AWS SNS
AWS IAM
Amazon Web Services AWS IAM
AWS S3
Amazon Web Services AWS S3
Amazon ElasticSearch Service
Kibana: Explore & Visualize Your Data
3DR DroneKit-Python

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)
Dremel
5/8" spade bit
3/4" spade bit
8mm drill bit

Story

Read more

Schematics

Wiring Diagram for Node

This shows how to connect the sensors directly to the Edison breakout board for one of our Nodes without the need for a Base Shield. Download Fritzing to zoom in high quality.

Wiring Diagram for Node Fritzing Image

Connection Details

Code

Edison_updated.js

JavaScript
//NOTE: This code is unfinished and the dependencies are incomplete. It should be used for reference only at this point  

//node.js deps

//npm deps

//app deps
const thingShadow = require('./thing');
//const isUndefined = require('../common/lib/is-undefined');
//const cmdLineProcess   = require('./lib/cmdline');

//UPM dependencies 
// Load Grove module
var groveSensor = require('jsupm_grove');


var moment = require('moment-timezone');

// Load MQ2 gas sensor module
var upmMQ2 = require("jsupm_gas"); 
// GPS library
var GPSSensor = require('jsupm_ublox6');

// Load RTC Clock module for Grove - RTC clock
var ds1307 = require('jsupm_ds1307');

var GPSSensor = require('jsupm_ublox6');



// Create the MQ2 gas sensor object using AIO pin 0
var MQ2_module = new upmMQ2.MQ2(0);

// Create the temperature sensor object using AIO pin 1
// Create the MQ5 gas sensor object using AIO pin 1
var MQ5_module = new upmMQ2.MQ5(1); // Already declared object of MQ2 sensor is used to save memory


//setup sensor on Analog pin #2 (A2)
var TP401_module = new upmMQ2.TP401(2); // Already declared object of MQ2 sensor is used to save memory

// Create the MQ9 gas sensor object using AIO pin 3
var MQ9_module = new upmMQ2.MQ9(3);

// Instantiate a Ublox6 GPS device on uart 0.
var myGPSSensor = new GPSSensor.Ublox6(0);     

// load this on i2c bus 0
var RTC_module = new ds1307.DS1307(0);



//Define your device name
var Device_Name = 'Node_1';


//exit device if GPS not found
if (!myGPSSensor.setupTty(GPSSensor.int_B9600))
{
  console.log("Failed to setup tty port parameters");
  process.exit(0);
}


// Collect and output NMEA data.
var bufferLength = 256;
var nmeaBuffer  = new GPSSensor.charArray(bufferLength);

//define AWS certificate path paramenters
var args = {


  privateKey:'/home/root/aws_certs/new/privateKey.pem',
  clientCert:'/home/root/aws_certs/new/cert.pem',
  caCert:'/home/root/aws_certs/new/rootCA.crt',
  clientId:'Node_1',
  region:'us-east-1', //at time of writing only region that supports AWS IoT
  reconnectPeriod:'10' //asumming reconnect period in seconds
} 


//create global state variable

var reported_state={ MQ2_gas: 0, MQ5_gas: 0, TP401_gas: 0, MQ9_gas:0, GPS_lat:0, GPS_long:0};

//create global sensor value variables:

//var read_lux = 0;

var read_MQ2 = 0;
var read_MQ5 = 0;
var read_TP401 = 0;
var read_MQ9 = 0;
var read_gpslat=null;
var read_gpslon=null;
var read_time=null;

//launch sample app function 
update_state(args);

function update_state(args) {

  //create a things Shadows object
  
  const thingShadows = thingShadow({
    keyPath: args.privateKey,
    certPath: args.clientCert,
    caPath: args.caCert,
    clientId: args.clientId,
    region: args.region,
    reconnectPeriod: args.reconnectPeriod,
  });

  //When Thing Shadows connects to AWS server:


  thingShadows
    .on('connect', function() {
      console.log('registering device: '+ Device_Name)
  
      //register device
      thingShadows.register(Device_Name);
  
      
      //read sensor values and send to AWS IoT every 5 seconds 
      setInterval(function(){
  
      read_sensor(send_state); 
  
    }, 15000);
  
    });
  
  
  //monitor for events in the stream and print to console:
  
  thingShadows 
    .on('close', function() {
      console.log('close');
    });
  thingShadows 
    .on('reconnect', function() {
      console.log('reconnect');
    });
  thingShadows 
    .on('offline', function() {
      console.log('offline');
    });
  thingShadows
    .on('error', function(error) {
      console.log('error', error);
    });
  thingShadows
    .on('message', function(topic, payload) {
      console.log('message', topic, payload.toString());
    });
  
  //define function for reading sensor
  function read_sensor(cb){

  //  read_lux = light.value();
  
      read_MQ2 = MQ2_module.getSample();
      read_MQ5 = MQ5_module.getSample();
      read_TP401 = TP401_module.getSample();
      read_MQ9 = MQ9_module.getSample();
      read_time=getDateTime();
    getGPSInfo();

    if(read_MQ2 > 10 ){
      thingShadows.publish('Data_1', JSON.stringify({ MQ2_gas: read_MQ2, MQ5_gas: read_MQ5, TP401_gas:read_TP401, MQ9_gas:read_MQ9, GPS_latitude:read_gpslat, GPS_longitude:read_gpslon,Time:read_time}));      //change this <--------------------------------------------------------------------

        
   }
   

    cb();

  };

  //define function for updating thing state:

  function send_state(){

    //define the payload with sensor values
    reported_state ={MQ2_gas: read_MQ2, MQ5_gas: read_MQ5, TP401_gas:read_TP401, MQ9_gas:read_MQ9, GPS_latitude:read_gpslat, GPS_longitude:read_gpslon}; //change this <--------------------------------------------------------------------

    //create state update payload JSON:
    device_state={state: { reported: reported_state }};

    //send update payload to aws:
    thingShadows.update(Device_Name, device_state );

  };

  
};

function getDateTime () {
  return moment().tz('America/Los_Angeles').format().slice(0, 19).replace(/T/, ' ');
   //return new Date().getTime();
}

function getGPSInfo()
{
  // we don't want the read to block in this example, so always
  // check to see if data is available first.
  if (myGPSSensor.dataAvailable())
  {
    var rv = myGPSSensor.readData(nmeaBuffer, bufferLength);

    var GPSData, dataCharCode, isNewLine, lastNewLine;
    var numlines= 0;
    if (rv > 0)
    {
      GPSData = "";
      // read only the number of characters
      // specified by myGPSSensor.readData
      for (var x = 0; x < rv; x++)
        GPSData += nmeaBuffer.getitem(x);
      // <<--------------------------->>
      var data_string=GPSData;
      if (data_string.substring(0,6) == '$GPGGA') {
              gpgga_string = data_string.split('\n')[0];
              gpgga_string = gpgga_string.replace('\n', '');
              gps_data = gpgga_string.split(',');
              console.log('data received: ' + gps_data);
             /* var time_string = gps_data[1];
              var gps_time = new Date();
              var gps_time_hours = parseInt(gps_data[1].substring(0,2));
              var gps_time_minutes = parseInt(gps_data[1].substring(2,4));
              var gps_time_seconds = parseInt(gps_data[1].substring(4,6));
              gps_time.setHours(gps_time_hours);
              gps_time.setMinutes(gps_time_minutes);
              gps_time.setSeconds(gps_time_seconds);
              */
              gps_lat = gps_data[2];
              gps_lat_ns = gps_data[3];
              gps_lon = gps_data[4];
              gps_lon_ew = gps_data[5];
              /*console.log('gps_time', gps_time);
              console.log(gps_lat, gps_lat_ns);
              console.log(gps_lon, gps_lon_ew);*/
                var temp;
                temp=decimalToDMS(gps_lat);
                read_gpslat=temp+ gps_lat_ns;
                //console.log('\n');
                //console.log(temp+ gps_lat_ns);
                temp=decimalToDMS(gps_lon);
                read_gpslon=temp+gps_lon_ew;
                //console.log(temp+gps_lon_ew);

          }





      //<<-------------------------->>
      
    }

    if (rv < 0) // some sort of read error occured
    {
      console.log("Port read error.");
      process.exit(0);
    }
  }
}



// Print message when exiting
process.on('SIGINT', function()
{
  console.log("Exiting...");
  process.exit(0);
});






function decimalToDMS(temp) {
  temp=parseFloat(temp);
deg=parseInt(temp/100);
temp=new String(temp);
temp=temp.slice(2,temp.length);
temp/=60;

temp=deg+temp;
temp=temp.toPrecision(10);
return temp;
}

Drone Control

Python
#!/usr/bin/python
'''
This code is based on drone kit project and a project by Mario Cannistr on hackster.io (Python and Paho for MQTT with AWS IoT)
This code doesn't have any warranty, expressed or implied. 
please execute this using python 3, to connect the drone, please use the proper IP address and port number

'''

import paho.mqtt.client as mqtt
import json, math, time, sys, ssl

from pymavlink import mavutil

import logging, logging.handlers, traceback

from dronekit import connect, Command
import time


# Amazon Webservices Descriptors
cert_path = "/home/ubuntu/Downloads/"
host = "A1KV2GD2H9GCVR.iot.us-east-1.amazonaws.com"
topic = "Data_2"
root_cert = cert_path + "root-CA.crt"
cert_file = cert_path + "8f02d2127a-certificate.pem.crt"
key_file = cert_path + "8f02d2127a-private.pem.key"

#Set up option parsing to get connection string
import argparse  
parser = argparse.ArgumentParser(description='Demonstrates mission import/export from a file.')
parser.add_argument('--connect', 
                   help="Vehicle connection target string. If not specified, SITL automatically started and used.")
args = parser.parse_args()

connection_string = args.connect
sitl = None


#Start SITL if no connection string specified
if not args.connect:
    print "Starting copter simulator (SITL)"
    from dronekit_sitl import SITL
    sitl = SITL()
    sitl.download('copter', '3.3', verbose=True)
    sitl_args = ['-I0', '--model', 'quad', '--home=-35.363261,149.165230,584,353']
    sitl.launch(sitl_args, await_ready=True, restart=True)
    connection_string='tcp:127.0.0.1:5760'


# Connect to the Vehicle
print 'Connecting to vehicle on: %s' % connection_string
vehicle = connect(connection_string, wait_ready=True)

# Check that vehicle is armable. 
# This ensures home_location is set (needed when saving WP file)

while not vehicle.is_armable:
    print " Waiting for vehicle to initialise..."
    time.sleep(1)


def readmission(aFileName):
    """
    Load a mission from a file into a list. The mission definition is in the Waypoint file
    format (http://qgroundcontrol.org/mavlink/waypoint_protocol#waypoint_file_format).

    This function is used by upload_mission().
    """
    print "\nReading mission from file: %s" % aFileName
    cmds = vehicle.commands
    missionlist=[]
    with open(aFileName) as f:
        for i, line in enumerate(f):
            if i==0:
                if not line.startswith('QGC WPL 110'):
                    raise Exception('File is not supported WP version')
            else:
                linearray=line.split('\t')
                ln_index=int(linearray[0])
                ln_currentwp=int(linearray[1])
                ln_frame=int(linearray[2])
                ln_command=int(linearray[3])
                ln_param1=float(linearray[4])
                ln_param2=float(linearray[5])
                ln_param3=float(linearray[6])
                ln_param4=float(linearray[7])
                ln_param5=float(linearray[8])
                ln_param6=float(linearray[9])
                ln_param7=float(linearray[10])
                ln_autocontinue=int(linearray[11].strip())
                cmd = Command( 0, 0, 0, ln_frame, ln_command, ln_currentwp, ln_autocontinue, ln_param1, ln_param2, ln_param3, ln_param4, ln_param5, ln_param6, ln_param7)
                missionlist.append(cmd)
    return missionlist


def upload_mission(aFileName):
    """
    Upload a mission from a file. 
    """
    #Read mission from file
    missionlist = readmission(aFileName)
    
    print "\nUpload mission from a file: %s" % import_mission_filename
    #Clear existing mission from vehicle
    print ' Clear mission'
    cmds = vehicle.commands
    cmds.clear()
    #Add new mission to vehicle
    for command in missionlist:
        cmds.add(command)
    print ' Upload mission'
    vehicle.commands.upload()


def download_mission():
    """
    Downloads the current mission and returns it in a list.
    It is used in save_mission() to get the file information to save.
    """
    print " Download mission from vehicle"
    missionlist=[]
    cmds = vehicle.commands
    cmds.download()
    cmds.wait_ready()
    for cmd in cmds:
        missionlist.append(cmd)
    return missionlist

def save_mission(aFileName):
    """
    Save a mission in the Waypoint file format 
    (http://qgroundcontrol.org/mavlink/waypoint_protocol#waypoint_file_format).
    """
    print "\nSave mission from Vehicle to file: %s" % export_mission_filename    
    #Download mission from vehicle
    missionlist = download_mission()
    #Add file-format information
    output='QGC WPL 110\n'
    #Add home location as 0th waypoint
    home = vehicle.home_location
    output+="%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % (0,1,0,16,0,0,0,0,home.lat,home.lon,home.alt,1)
    #Add commands
    for cmd in missionlist:
        commandline="%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % (cmd.seq,cmd.current,cmd.frame,cmd.command,cmd.param1,cmd.param2,cmd.param3,cmd.param4,cmd.x,cmd.y,cmd.z,cmd.autocontinue)
        output+=commandline
    with open(aFileName, 'w') as file_:
        print " Write mission to file"
        file_.write(output)
        
        
def printfile(aFileName):
    """
    Print a mission file to demonstrate "round trip"
    """
    print "\nMission file: %s" % aFileName
    with open(aFileName) as f:
        for line in f:
            print ' %s' % line.strip()        


def drone_response_req():  
    cmds = vehicle.commands
    import_mission_filename = 'waypoint.txt'
        
    #Upload mission from file
    upload_mission(import_mission_filename)

    #Close vehicle object before exiting script
    vehicle.mode    = VehicleMode("GUIDED")
    vehicle.armed   = True
    vehicle.flush()

    while not vehicle.armed and not api.exit:
        print " Waiting for arming..."
    time.sleep(1)

    print "Taking off!"
    vehicle.commands.takeoff(10) # Take off to target altitude
    vehicle.flush()
    print "Starting mission"
    # Set mode to AUTO to start mission
    vehicle.mode = VehicleMode("AUTO")
    vehicle.flush()

    print "Close vehicle object"
    vehicle.close()

    # Shut down simulator if it was started.
    if sitl is not None:
        sitl.stop()


# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    client.subscribe(topic)

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    print("payload: "+str(msg.payload))
    data=str(msg.payload);
    ind=data.find('goto_node_1')
    if(ind>-1):
        print ('command requested! ')
        drone_response_req
    else:
        print("Not found!")

   
client = mqtt.Client(client_id="drone_control.py")
client.on_connect = on_connect
client.on_message = on_message
client.on_log = on_log
client.tls_set(root_cert,
               certfile = cert_file,
               keyfile = key_file,
               tls_version=ssl.PROTOCOL_TLSv1_2,
               ciphers=None)

client.connect(host, 8883, 60)

client.loop_forever()

Sample waypoints file

Plain text
QGC WPL 110
0	1	0	16	0	0	0	0	42.353613	-83.077283	1.000000	1
1	0	3	22	15.000000	0.000000	0.000000	0.000000	0.000000	0.000000	10.000000	1
2	0	3	18	5.000000	0.000000	5.000000	0.000000	42.353276	-83.078581	100.000000	1
3	0	3	20	0.000000	0.000000	0.000000	0.000000	42.353589	-83.077272	100.000000	1
4	0	3	21	0.000000	0.000000	0.000000	0.000000	42.353589	-83.077272	0.000000	1

Credits

IoTWarriors

IoTWarriors

1 project • 4 followers
IoT Warriors is a team comprised of three Wayne State University Engineering students (Pranav Chokda, Kalmajit Chahal, Chris Gregory).
Pranav Chokda

Pranav Chokda

1 project • 2 followers
Kamaljit Chahal

Kamaljit Chahal

2 projects • 3 followers
Electrical engineering recent graduate

Comments