Md. Khairul Alam
Published © MIT

LoRa Based Smart City Air Quality Monitoring

A solar-powered environmental monitoring system based on the Reyax LoRa module and Grandeur IoT Cloud platform for a smart city.

IntermediateFull instructions provided10 hours6,664
LoRa Based Smart City Air Quality Monitoring

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
Base Shield V2
Seeed Studio Base Shield V2
×1
NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
×1
Loudness Sensor
Seeed Studio Loudness Sensor
×1
Grove - Air quality sensor v1.3
Seeed Studio Grove - Air quality sensor v1.3
×1
Seeed Studio Seeed Grove - Temperature & Humidity Sensor (DHT11)
×1
Reyax LoRa Module
×1
Seeed Studio Small Solar Panel 80x100mm 1W
×1
Seeed Studio Seeed LiPo Rider Pro
×1
Rechargeable Battery, 3.7 V
Rechargeable Battery, 3.7 V
×1

Software apps and online services

Arduino IDE
Arduino IDE
Grandeur
Grandeur
Grandeur Canvas
Grandeur Canvas

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Main Box STL

Box Front Cover STL

Schematics

LoRa Module Datasheet

LoRa Module AT command set

Code

Code for Sensor Node

Arduino
This code is for the sensor device that collects sensors data and sent the data to the IoT gateway using LoRa.
#include "AirQuality.h" //grove air quality library
#include "Arduino.h"
#include "DHT.h"

#define DHTPIN 4     // what digital pin DHT sensor connected to
#define DHTTYPE DHT11   // model of the sensor DHT11 

DHT dht(DHTPIN, DHTTYPE);

AirQuality airqualitysensor;
int current_quality =-1;

const int sampleWindow = 50;                              // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;
float noise;
float temp, humid;
int airQuality;

 //--------------------------------------------------------------------------------------------
 //                                            SETUP
 //--------------------------------------------------------------------------------------------
void setup() 
{
   
   Serial.begin(115200);                  //Serial for lora
   airqualitysensor.init(14);
   dht.begin();

}
 
//--------------------------------------------------------------------------------------------
//                                         MAIN LOOP
//--------------------------------------------------------------------------------------------
  
void loop() 
{
 noise = calculate_sound_in_db();
 airQuality = calculate_air_quality();
 calculate_temp_humid();

 String temperature = String(temp);
 String humidity = String(humid);
 String sound = String(noise);
 String air = String(airQuality);
 
 String values = String(temperature)+","+ String(humidity)+","+ String(sound)+","+ String(air);
 String cmd = "AT+SEND=0,"+String(values.length())+","+values; //AT+SEND=<Address>,<Payload Length>,<Data>
 Serial.println(cmd); //sent to lora
  
 delay(15000);
}

//this function calculate sound level in dB
float calculate_sound_in_db(){
   unsigned long startMillis= millis();                   // Start of sample window
   float peakToPeak = 0;                                  // peak-to-peak level
 
   unsigned int signalMax = 0;                            //minimum value
   unsigned int signalMin = 1024;                         //maximum value
 
                                                          // collect data for 50 mS
   while (millis() - startMillis < sampleWindow)
   {
      sample = analogRead(A1);                             //get reading from microphone
      if (sample < 1024)                                  // toss out spurious readings
      {
         if (sample > signalMax)
         {
            signalMax = sample;                           // save just the max levels
         }
         else if (sample < signalMin)
         {
            signalMin = sample;                           // save just the min levels
         }
      }
   }
   peakToPeak = signalMax - signalMin;                    // max - min = peak-peak amplitude
   //Serial.println(peakToPeak);                                     //write calibrated deciBels
   float db = map(peakToPeak,0,1000,48,120);             //calibrate for deciBels
   //Serial.print(db);                                     //write calibrated deciBels
   //Serial.println(" dB");                                  //write units
   return db;
}

int calculate_air_quality(){
  current_quality=airqualitysensor.slope();
  /*
    if (current_quality >= 0)// if a valid data returned.
    {
        if (current_quality==0)
            Serial.println("High pollution! Force signal active");
        else if (current_quality==1)
            Serial.println("High pollution!");
        else if (current_quality==2)
            Serial.println("Low pollution!");
        else if (current_quality ==3)
            Serial.println("Fresh air");
    }
  */
  return current_quality;
}

void calculate_temp_humid(){
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(t)) {
    //Serial.println("Failed to read from DHT sensor!");
    return;
  }

  temp = t;
  humid = h;
  /*
  Serial.print("Humidity: ");
  Serial.print(h);
  Serial.print(" %\t");
  Serial.print("Temperature: ");
  Serial.print(t);
  Serial.print(" *C ");
  */
}

//interrupt service routine for air quality sensor
ISR(TIMER1_OVF_vect)
{
  if(airqualitysensor.counter==61)//set 2 seconds as a detected duty
  {

      airqualitysensor.last_vol=airqualitysensor.first_vol;
      airqualitysensor.first_vol=analogRead(A0);
      airqualitysensor.counter=0;
      airqualitysensor.timer_index=1;
      PORTB=PORTB^0x20;
  }
  else
  {
    airqualitysensor.counter++;
  }
}

Code for Gateway

Arduino
Receives data from the sensor nodes, process the data and sends to Grandeur cloud
/* Including the SDK and WiFi library */
#include <Grandeur.h>
#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>

SoftwareSerial LORA(D7, D8);

/* Configurations */
String deviceID = "your device id";
String apiKey = "your api key";
String token = "your device token";

/* WiFi credentials */
String ssid = "your wifi ssid";
String password = "your wifi password";

/* Create variable to hold project and device */
Grandeur::Project project;
Grandeur::Project::Device device;


/* Variable to keep track of connection state and time */
int connected = 0;
uint32_t lastUpdate = 0;

String inputString = "";         // a string to hold incoming data
boolean stringComplete = false;  // whether the string is complete
//: and % was set from the transmitter node for the eage of data separation
char start_val = ':';
char end_val = '%';
int start_pos = 0;
int end_pos = 0;

float temp = 0;
float humid = 0;
float air = 0;
float noise = 0;


/* Function to check device's connection status */
void onConnection(bool status) {
  switch(status) {
        case CONNECTED:
          Serial.println("Device is connected to the cloud.");
          /* Record connection state and time */
          connected = 1;       
          lastUpdate = millis();
          return;

        case DISCONNECTED:
          Serial.println("Device is disconnected from the cloud.");
          connected = 0;
          return;
  }
}

/* Function to connect to WiFi */
void connectWiFi() {
    /* Set mode to station */
    WiFi.mode(WIFI_STA);
    /* Connect using the ssid and password */
    WiFi.begin(ssid, password);

    /* Block till the WiFi is connected */
    while (WiFi.status() != WL_CONNECTED) {
         delay(500);
         Serial.print(".");
    }
    
    /* Print message */
    Serial.println("");
    Serial.println("WiFi connected");
    /* And IP address */
    Serial.println(WiFi.localIP());
}

/* Function to confirm that data has been updated */
void dataCallback(const char* code, Var res) {
  /* If the update was recorded successfully */
  if(code == "DEVICE-DATA-UPDATED") {
    //Serial.println("Updated DATA");
    /* Get data */
    double temp_data = (double) res["update"]["temp"];  
    double humid_data = (double) res["update"]["humid"];  
    double noise_data = (double) res["update"]["noise"];  
    double air_data = (double) res["update"]["air"];    

    /* Print */
    Serial.printf("Temperature: %f Humidity: %f Noise Level: %f Air Quality: %f\n", temp_data, humid_data, noise_data, air_data);
    
    return;
  }
  
  /* If the summary could not be updated. */
  Serial.println("Failed to Update DATA");
  return;
}

/* Function to send updated current and power readings */
void sendUpdate() {

    Var data;
    //JSON data format set in Grandeur: {"temp":0, "humid":0, "air":0, "noise":0}
    data["temp"] = temp;
    data["humid"] = humid;
    data["noise"] = noise;
    data["air"] = air;

    /* Record update */
    //device.data().set("", data, dataCallback); //send JSON
    device.data().set("temp", temp, dataCallback);
    device.data().set("humid", humid, dataCallback);
    device.data().set("noise", noise, dataCallback);
    device.data().set("air", air, dataCallback);
    //delay(2000);
}

void setup() {
    /* Begin the serial */
    Serial.begin(9600);
    LORA.begin(115200);
    inputString.reserve(200);
    /* Connect to WiFi */
    connectWiFi();   
    /* Initializes the global object "grandeur" with your configurations. */
    project = grandeur.init(apiKey, token);    
    /* Get reference to device */
    device = project.device(deviceID);    
    /* Sets connection state update handler */
    project.onConnection(onConnection);
}

void loop() {
    serialEvent();
    project.loop(WiFi.status() == WL_CONNECTED);

    /* Send update to server */
    if (stringComplete) {
      process_received_data();
      if (connected) sendUpdate();
    }
}


void serialEvent() {
  while (LORA.available()) {    
    // get the new byte:
    char inChar = (char)LORA.read();     
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
    else
    // add it to the inputString:  
      inputString += inChar;
  }
}

void process_received_data(){
  
    start_pos = inputString.indexOf(start_val);
    end_pos = inputString.indexOf(end_val);
    String data_string = inputString.substring(start_pos+1, end_pos-1);
    //Serial.println(data_string);
    //identifying commas inn the string
    int firstCommaIndex = data_string.indexOf(',');
    int secondCommaIndex = data_string.indexOf(',', firstCommaIndex+1);
    int thirdCommaIndex = data_string.indexOf(',', secondCommaIndex+1);
    //seperating comma seperated value from the data string  
    String temperature = data_string.substring(0, firstCommaIndex);
    String humidity = data_string.substring(firstCommaIndex+1, secondCommaIndex);
    String noise_level = data_string.substring(secondCommaIndex+1, thirdCommaIndex);
    String air_auality = data_string.substring(thirdCommaIndex+1);
    
    //Serial.println(temperature);
    //Serial.println(humidity);
    //Serial.println(noise);
    //Serial.println(air_auality);

    temp = temperature.toFloat();
    humid = humidity.toFloat();
    noise = noise_level.toFloat();
    air = air_auality.toFloat();  

    Serial.println(temp);
    Serial.println(humid);
    Serial.println(noise);
    Serial.println(air);
    
    inputString = "";
    stringComplete = false;
 }

Credits

Md. Khairul Alam

Md. Khairul Alam

64 projects • 568 followers
Developer, Maker & Hardware Hacker. Currently working as a faculty at the University of Asia Pacific, Dhaka, Bangladesh.

Comments