Introduction: LoRa Based Smart City Air Quality Monitoring

About: I like to learn, like to make, like to share.

Smart City & Air Quality

The World Bank defines a smart city as one that is a technology-intensive city. This means highly efficient public services. One of the fundamental features of smart cities is providing a sustainable environment. For a sustainable environment, it is necessary to monitor the environmental conditions for identifying the sources of pollution and mitigate them. In this context, thousands of air quality sensors are placed to provide real-time information to the government and citizens alike. This data can be turned into useful information, allowing us to make better decisions – whether to do with transport planning or knowing what route is best to walk to work. Air quality sensing fits well with the vision of the smart city – providing information about a city that was not previously available, allowing people to make decisions that can improve the quality of their lives.

Problem & Solution

The increased level of air pollution in big cities has become a major concern because of the risk it represents to human health. In this context, technology has become a very useful tool in contamination monitoring and the possible mitigation of its impact. Particularly, there are different proposals using the internet of things (IoT) paradigm that uses interconnected sensors in order to measure different pollutants. A network of low-cost air quality monitoring sensor nodes can be deployed to monitor air quality and meteorological parameters. Hence, through pollution source detection, the city can take corrective measures and improve its environmental health. By installing disaster detection systems like floods and rainfall monitoring solutions in your surrounding. the citizens can be alerted beforehand in case of a catastrophic event. A holistic view is derivable, enabling the authorities to make data-driven infrastructure and policy planning decisions.

Supplies

1. Arduino UNO

2. Seeed Base Shield V2

3. NodeMCU ESP8266 Breakout Board

4. Seeed Loudness Sensor

5. Seeed Grove - Air quality sensor v1.3

6. Seeed Grove - Temperature & Humidity Sensor (DHT11

7. Reyax LoRa Module

8. Seeed Small Solar Panel 80x100mm 1W

9. Seeed LiPo Rider Pro

10. Rechargeable Battery, 3.7 V

If you want Wi-Fi, Bluetooth, GPS and LoRa in a single chip you can try with Wio-WM1110 module. You can use this module for any IoT application like asset tracking, inventory management, asset loss, and theft prevention. It is perfect for smart agriculture, wireless meter reading, and smart city applications. To know more about the Wio-WM1110 module in details read this blog from Seeedstudio.

Step 1: The Project Plan

In this project, we are going to monitor a few most important environmental parameters like noise, temperature, humidity, and air quality. It transmits real-time data through wireless communication protocol to a cloud platform. The equipment fully runs on solar power with a battery backup. The data from the device is accessible on a dashboard that visualizes and analyzes the data in the desired formats.

For smart city air quality monitoring, you have to place sensor nodes in different locations. In terms of power consumption and cost, it is not a good idea to connect every sensor node individually to the cloud. Rather it is a good idea to collect data from all the sensor nodes of a small area and publish all the data to the cloud from a single gateway. LoRa can give us this advantage and that's why I have chosen LoRa for my project.

For the cloud service, I am using Grandeur for my project. Grandeur is a comparatively new maker-friendly IoT platform where we can store our data and visualize the data by making graph widgets using the drag and drop method. They also provide Arduino, JavaScript, and Python SDK with example code. So, one can easily connect with their service using Arduino, NodeMCU, and Raspberry Pi.

The best way to power a sensor node is to use a solar panel. Because sensor nodes are placed in a different remote location and changing the battery frequently or providing power from a utility source is not always possible. I am providing power to my sensor note using the solar panel and Li-ion battery.

Step 2: Making the Sensor Node

I want to monitor air quality, noise level, temperature & humidity. So I used the Grove air quality sensor, Grove noise sensor, and Grove DHT11 temperature & humidity sensor. One good thing about Grove sensors is that you can connect all grove sensors to Arduino using Grove base shield without any soldering.

Arduino, Grove Base Shield, Lipo Rider Pro, Sensors and Solar Panel

For powering the circuit I used Grove 1W solar panel and Li-ion battery. For charging the battery using the solar panel I used Seeed LiPo Rider Pro solar charger that has a built-in 5V boost converter. So we can easily run Arduino using the output of the LiPo Rider.

Most of the components used here are available on Seeedstudio.com. The following image shows a plug-and-play test connection for all the components for the sensor node.

Test connection

For transmitting the sensor data to the IoT gateway I used Reyax LoRa transceiver. The following image shows the Reyax RYLR998 Lora Module with the pin diagram.


Reyax LoRa

Preparing Reyax RYLR998 LoRa Engine to Connect with Arduino Base Shield

The RYLR998 has 5 male pins output and is not Grove base shield compatible. For connecting it to Grove Shield I soldered a Grove cable in a perfboard with a 5 pin female header. The circuit connection is as follows:

The RYLR998 LoRa module can operate safely up to 3.6V. Arduino TX pin produces 5V output that is not safe for the LoRa. So, I used a voltage divider to get 3.4 V from the Arduino TX pin. On the other side, Arduino RX pin workes perfectly on 3.3V logic. So, I connected the TX pin of the LoRa to the RX pin of the Arduino directly. For normal operation, the RST pin of the LoRa should be in the high state.

Now the above LoRa module can easily be connected to the base shield using the following converter circuit.

But before connecting the Module to the base shield make sure that the base shield power button is in 3.3V position as like as the image below.


Base shield voltage selection

After connecting the LoRa module to the base shield we can test by sending AT command to the module from Arduino.


Step 3: Communication Using RYLR896 LoRa Transceiver

In order to establish communication between two LoRa modules, they must be able to find each other by an address. The addressing scheme of the LoRa ecosystem depends a bit on the modules and implementation. Simply speaking, the module enables us to set a network id and an address. Modules on the same network (same network id) can communicate with each other. Each module is supposed to have a unique id (address) within a network. If a module has an address 0, that module is able to receive data from all the devices on the network.

Every module comes with a default frequency, network id, and address but you may need to change the default value. The default network id for the above module is 18 and the address to 0.

We don't need to change anything at this moment for our demo project. But if you like to change any parameter please check the datasheet for the details of the AT commands.

Sample AT command responses

In order to send a message (HELLO RECEIVER!), we just need to send the command “AT+SEND=0, 15, HELLO RECEIVER!” at the serial connection of the module. The first parameter value ‘0’ is the address of the receiver module. The next value ’15’ is the number of characters/bytes to be sent. The third parameter value is the message to be sent: ‘HELLO RECEIVER!’ (15 characters).

Code for Sensor Node

The following code is developed for the sensor node to collect all the sensor data and transmit it to the IoT gateway.

#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++;
}
}

The code sends the sensor data every 15 seconds. Change the timing according to your requirement.

Step 4: Placing Components Inside the Box

Now we will place all the sensors and battery inside the box. First, 3D print the two STL files attached in the attachment section. The printed images look likes the following image.

1 / 3

The complete box will look like following. You may require some sending base on the quality of your printer.


After preparing the box first attached the solar panel at the top of the box. The the connecting wires inside the box through the top hole.

1 / 2

Then place the Arduino with attached base shield first with some hot gule or double sided tape. Place other sensors one by one and attache them in a fixed place using some hot glue.

1 / 3

Place battery and lipo rider pro and finally add the LoRa module.


Sensor Node is ready to deploy.



Step 5: Configuring Grandeur

Grandeur provides a backend service for IoT device where a virtual device-user pair is created. The device model is described upon creation as a JSON object containing various variables (temperature, humidity, noise_level, air_quality in our case) which can be read and written to. Grandeur Canvas is a web app that can be used to visualize the device state of paired devices using graphical widgets.

For using the Grandeur, we have to make our account and do some important configurations on the platform. Check out their website https://grandeur.dev/to get into the game.

1. After login to the root account create a new project:

2. Create a new user account from the Add account option from Home tab or from accounts tab. We will later sign into our app with the email and password we create now. This is another cool feature of Grandeur: multiple users can use your app by signing in with their emails and passwords. Each user can pair its own devices and device paired by one user becomes unavailable for others. Authentication is built at the core of Grandeur. User creation is illustrated below:


3. Now create a new device through devices tab. This operation generates a new unique device ID which we'll use while writing code for our hardware and this device ID is required to connect to the Grandure using API. Creating a device also generates an accessToken. Don't forget to copy it and it will be utilized in hardware code and won't be accessible after closing the dialog. You have to add a device model before adding a device using the following procedure. A device model is used to describe what variables can be communicated by/to the device.


From the models click on Add and give a model name. In the Schema put all the variable (4 in our case) in JSON format. Keep value 0 at this time. These variable will be updated automatically when we will send data from our device node.

 {"temp":0, "humid":0, "air":0, "noise":0}

After putting the schema click on Add.


After adding the device model click on Add from Devices option and select the model you just created to link the model to the device.


Give a device id and click to Register.


Copy the access token and it will be utilized in hardware code and won't be accessible after closing the dialog.


Device is created successfully and note down the DEVICE ID also. It will also be required for coding.


A user cannot interact with a device unless it's paired with it. There are two ways you can pair your device: 1) using the Cloud Dashboard, or 2) through the web app by using pairDevice() function of the device API. This second way has a very strong significance if you look at it from production point of view. Like you can now ship your hardware products, and your users can sign in to your app and declare ownership over a hardware by pairing it. Here's how you can pair your device with a user using the Cloud Dashboard:

To do pair the device click on Pair and choose an account. You are ready to go to next step.

4. We will create a canvas now.

In the left menu on dashboard, click on canvas, or go to https://canvas.grandeur.tech. If this is your first time visiting Canvas, an Authorize button will appear. Authorization gives Canvas access to your project's users and devices. Clicking on "Authorize" button will redirect you back to Grandeur dashboard where you'd be prompted to choose the project you want Canvas to bind itself to.

Choose a graph widgets and click on it. Configure menu will be apeared and then click to configure to give a title of the graph and set the variable whose value you want to show in this graph.

Click to Save.


Following the same procedure add 4 graph widgets for the 4 variables.


After connecting Canvas to your project, you may see a login screen. You need to log in here as a user of your project that you created earlier, for which you need to create a user account in your project. You can do that by visiting accounts page of the dashboard. Then you can log in to Canvas by using the credentials of this new user.

Step 6: Preparing IoT LoRa Gateway

In a simple way, an IoT Gateway is like home or office network router or gateway that connects sensors, IoT modules, and smart devices to the cloud. Such a gateway facilitates communication between your devices, maintains security, and provides an admin interface where you can perform basic functions.

A true IoT gateway contains communication technologies connecting end-devices (sensors, actuators, or more complex devices) and backend platforms (data, device, and subscriber management) to the gateway. It has a computing platform allowing pre-installed or user-defined applications to manage data (for routing and computing at the edge), devices, security, communication, and other aspects of the gateway.

Raspberry Pi can be a good option for a gateway with lots of flexibility but to keep it simple I am using Node MCU as a gateway for this project. Node MCU will receive the data from the LoRa module using UART. Then using Arduino SDK we will upload the data to the Grandeur cloud platform.


Firmware for the Gateway

The following code was developed for the gateway using Arduino SDK provided by Grandeur cloud. Data is received in string form from the LoRa using the UART port. Then the received data is processed to separate the individual variable from the comma-separated string value. The values are then sent to Grandeur using WiFi. The following function was used to separate the variables.

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;
}

The complete code is attached in the code section.


Step 7: Data Visualization

Data Visualization