Hardware components | ||||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 2 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
| ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
The story behind creating a portable air quality monitoring device that attaches to a drone for cities around the United States is one born from technological innovation. It starts with recognizing the critical need for accurate, real-time data on air quality in urban areas, where pollution levels can fluctuate rapidly and have significant impacts on public health.
Perhaps environmental scientists, engineers, or concerned citizens themselves, witnessed firsthand the challenges faced by cities in monitoring and managing air quality effectively. Some limitations of traditional stationary monitoring stations often provide only localized data and can't capture the full picture of air quality dynamics across a city.
Driven by a desire to address this gap, the journey began in developing a solution that could offer comprehensive, actionable insights into air quality on a citywide scale. Drawing on my newfound expertise in sensor technology, drone technology, and data analysis, I
envisioned a portable device that could be mounted onto drones, allowing for agile, flexible monitoring across different locations and elevations.
/*
* Project Portable Air Quality Monitoring devices
* Description: will monitor air quality, temp, humidity and gps locations while sampling the air quality
* Author: Elsmen Aragon
* Date: 24-MAR-2023
*/
// NOTE: The Adafruit_GPS header file has been modified to work with the Particle environment DO NOT INSTALL Adafruit_GPS USE the library from 14_04
#include "Particle.h"
#include "Adafruit_GPS.h"
#include "IoTTimer.h"
#include "credentials.h"
#include "Adafruit_BME280.h"
#define SENSOR_ADDRESS 0x40 // Change this to the address obtained from the I2C scan
Adafruit_BME280 myReading; //Defining bme object mine is called myReading
Adafruit_GPS GPS(&Wire);
IoTTimer sampleTimer;// timer for led onboard
// Define User and Credentials
String password = "------------------"; // AES128 password
String myName = "borrowed";
// Define Constants
const int RADIOADDRESS = 0xA2; // it will be a value between 0xA1 and 0xA9 for my case
const int TIMEZONE = -6;
const unsigned int UPDATEGPS = 5000;
const int RADIONETWORK = 1; // range of 0-16
const int SENDADDRESS = 0xA1; // address of radio to be sent to
//Delcare variable for air quality sensor
byte data[29];
//int aqOne;
//int aqOneResult;
const int AQTIMER = 5000;
int aq, aq2, aq10;
// Declare Variables for gps
float lat, lon, alt;
int sat;
unsigned int lastGPS;
//Declare varibles for BME280
const int HEXADDRESS = 0X76;
bool status;
float temperature, humidity;
// Declare Functions
void getBme(float *temperature, float *humidity);
void getAirQuality(int *aq, int *aq2, int *aq10);
//int getAirQuality();
void getGPS(float *latitude, float *longitude, float *altitude, int *satellites);
void sendData(float temp, int aq, float latt, float lonn, float altt, float humidd, int aq2, int aq10);
void reyaxSetup(String password);
SYSTEM_MODE(SEMI_AUTOMATIC);
void setup() {
Serial.begin(9600);
waitFor(Serial.isConnected, 5000);
Serial1.begin(115200);
reyaxSetup(password);
//Initialize GPS
GPS.begin(0x10); // The I2C address to use is 0x10 from I2C scan
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
GPS.sendCommand(PGCMD_ANTENNA);
delay(1000);
GPS.println(PMTK_Q_RELEASE);
// Initialize I2C communication for air Quality HM3301
Wire.beginTransmission(SENSOR_ADDRESS);
if (Wire.endTransmission() == 0) {
Serial.println("Sensor found at address 0x40");
}
else {
Serial.println("No sensor found at address 0x40.");
}
//BME Check
status = myReading.begin(HEXADDRESS);
if ( status == false ) {
Serial.printf(" BME280 at address 0x%02x X failed to start ", HEXADDRESS );
}
//Timer for sample air quality + other data
sampleTimer.startTimer(AQTIMER);
}
void loop() {
//Get data from GPS unit (best if you do this continuously)
GPS.read();
if (GPS.newNMEAreceived()) {
if (!GPS.parse(GPS.lastNMEA())) {
return;
}
}
if (millis() - lastGPS > UPDATEGPS) {
lastGPS = millis(); // reset the timer
getGPS(&lat,&lon,&alt,&sat);
Serial.printf("\n=================================================================\n");
Serial.printf("Lat: %0.6f, Lon: %0.6f, Alt: %0.6f, Satellites: %i\n",lat, lon, alt, sat);
Serial.printf("=================================================================\n\n");
}
if(sampleTimer.isTimerReady()){
getAirQuality(&aq, &aq2, &aq10);
//aqOneResult = getAirQuality();
Serial.printf("Lat: %0.6f, Lon: %0.6f, Alt: %0.6f, Satellites: %i\n",lat, lon, alt, sat);
getBme(&temperature, &humidity);
Serial.printf("Humidity: %.2f%%\nTempF: %.2f\n", humidity, temperature);
//tempResult = getBme();
//Serial.printf("TempF = %02f\n", tempResult);
sampleTimer.startTimer(AQTIMER);
sendData(temperature, aq, lat, lon, alt, humidity, aq2, aq10);
}
}
void getAirQuality(int *aq, int *aq2, int *aq10){
//Serial.printf("im here");
Wire.requestFrom(SENSOR_ADDRESS, 29); // Request 29 bytes of data
delay(100); // Allow time for sensor to respond
// Check if data is available
if (Wire.available() >= 29) { //??
// Read response from sensor
for (int i = 0; i < 29; i++) {
data[i] = Wire.read();
}
*aq = data[5] | data[6];
*aq2 = data[7] | data[8];
*aq10 = data[11] | data[12];
Serial.printf("sensor# = %i\nPM 1.0 = %i\nPM 2.5 = %i\nPM c10 = %i\n", data [3] | data[4], data[5] | data[6], data[7] | data[8], data[11] | data[12]);
}
}
// Get GPS data
void getGPS(float *latitude, float *longitude, float *altitude, int *satellites) {
int theHour;
theHour = GPS.hour + TIMEZONE;
if (theHour < 0) {
theHour = theHour + 24;
}
Serial.printf("Time: %02i:%02i:%02i:%03i\n", theHour, GPS.minute, GPS.seconds, GPS.milliseconds);
Serial.printf("Dates: %02i-%02i-%02i\n", GPS.month, GPS.day, GPS.year);
Serial.printf("Fix: %i, Quality: %i", (int)GPS.fix, (int)GPS.fixquality);
if (GPS.fix) {
*latitude = GPS.latitudeDegrees;
*longitude = GPS.longitudeDegrees;
*altitude = GPS.altitude;
*satellites = (int)GPS.satellites;
}
}
void getBme(float *temperature, float *humidity){
float tempC, pressPA, humidRH, tempF, convertedPA;
tempC = myReading.readTemperature ();
pressPA = myReading.readPressure ();
convertedPA = pressPA*0.00029530;
humidRH = myReading.readHumidity ();
*humidity = humidRH;
tempF = (tempC*9/5)+32;
*temperature = tempF;
}
// Send data to IoT Classroom LoRa basestation in format expected
void sendData(float temp, int aq, float latt, float lonn, float altt, float humidd, int aq2, int aq10) {
char buffer[60];
sprintf(buffer, "AT+SEND=%i,60,%0.2f,%i,%0.6f,%0.6f,%0.6f,%0.2f,%i,%i\r\n", SENDADDRESS, temp, aq, latt, lonn, altt, humidd, aq2, aq10);
Serial1.printf("%s",buffer);
//Serial.printf("buff: %s", buffer);
//Serial1.println(buffer);
delay(1000);
if (Serial1.available() > 0)
{
Serial.printf("Awaiting Reply from send\n");
String reply = Serial1.readStringUntil('\n');
Serial.printf("Send reply: %s\n", reply.c_str());
}
}
// Configure and Initialize Reyax LoRa module
void reyaxSetup(String password) {
// following four paramaters have most significant effect on range
// recommended within 3 km: 10,7,1,7
// recommended more than 3 km: 12,4,1,7
const int SPREADINGFACTOR = 10;
const int BANDWIDTH = 7;
const int CODINGRATE = 1;
const int PREAMBLE = 7;
String reply; // string to store replies from module
Serial1.printf("AT+ADDRESS=%i\r\n", RADIOADDRESS); // set the radio address
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply from address\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Reply address: %s\n", reply.c_str());
}
Serial1.printf("AT+NETWORKID=%i\r\n", RADIONETWORK); // set the radio network
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply from networkid\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Reply network: %s\n", reply.c_str());
}
Serial1.printf("AT+CPIN=%s\r\n", password.c_str());
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply from password\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Reply: %s\n", reply.c_str());
}
Serial1.printf("AT+PARAMETER=%i,%i,%i,%i\r\n", SPREADINGFACTOR, BANDWIDTH, CODINGRATE, PREAMBLE);
delay(200);
if (Serial1.available() > 0) {
reply = Serial1.readStringUntil('\n');
Serial.printf("reply: %s\n", reply.c_str());
}
Serial1.printf("AT+ADDRESS?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Radio Address: %s\n", reply.c_str());
}
Serial1.printf("AT+NETWORKID?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Radio Network: %s\n", reply.c_str());
}
Serial1.printf("AT+PARAMETER?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("RadioParameters: %s\n", reply.c_str());
}
Serial1.printf("AT+CPIN?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Radio Password: %s\n", reply.c_str());
}
}
/*
* Project Portable Air Quality Monitoring devices
* Description: will monitor air quality, temp, humidity and gps locations while sampling the air quality this is for the base station
* Author: Elsmen Aragon
* Date: 24-MAR-2023
*/
#include "Particle.h"
#include <Adafruit_MQTT.h>
#include "Adafruit_MQTT/Adafruit_MQTT_SPARK.h"
#include "Adafruit_MQTT/Adafruit_MQTT.h"
#include "credentials.h"
#include <JsonParserGeneratorRK.h>
#include "IoTTimer.h"
String password = "----------------------"; // AES128 password
String myName = "Thor2";
const int RADIOADDRESS = 0xA1; // It will be a value between 0xC1 - 0xCF can be anything though up to 255
const int TIMEZONE = -6;
unsigned int last, lastTime;
IoTTimer publishTimer;
const int PUBTIME = 12000;
// Define Constants
const int RADIONETWORK = 1; // range of 0-16
const int SENDADDRESS = 0xA2; // address of radio to be sent to if I send back to another LoRa device
void reyaxSetup(String password);
//void sleepULP();
/************ Global State (you don't need to change this!) *** ***************/
TCPClient TheClient;
// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_SPARK mqtt(&TheClient,AIO_SERVER,AIO_SERVERPORT,AIO_USERNAME,AIO_KEY);
// Setup Feeds to publish or subscribe
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>
Adafruit_MQTT_Publish tempDrone = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/capTemp");
Adafruit_MQTT_Publish humidityDrone = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/capHumidity");
Adafruit_MQTT_Publish airOneDrone = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/capAirOne");
Adafruit_MQTT_Publish airTwoDrone= Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/capAirTwo");
Adafruit_MQTT_Publish airTenDrone = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/capAirTen");
Adafruit_MQTT_Publish gpsDrone = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/capGPS");
/************Declare Functions*************/
void MQTT_connect();
bool MQTT_ping();
//Declare function for json collector
void createEventPayLoad (float lat, float lon, float alt);
// Let Device OS manage the connection to the Particle Cloud comment out or put in manual when connecting to mqtt
//SYSTEM_MODE(AUTOMATIC);
void setup() {
Wire.begin();
Serial.begin(9600);
waitFor(Serial.isConnected, 10000);
Serial1.begin(115200);
delay(5000);
reyaxSetup(password);
WiFi.on();
WiFi.connect();
while(WiFi.connecting()) {
Serial.printf(".");
}
Serial.printf("\n\n");
publishTimer.startTimer(PUBTIME);
}
void loop() {
MQTT_connect();
MQTT_ping();
if (Serial1.available()) { // full incoming buffer: +RCV=203,50,mydata,
//Serial.printf("here i am ");
String parse0 = Serial1.readStringUntil('='); //+RCV
String parse1 = Serial1.readStringUntil(','); // address received from device
String parse2 = Serial1.readStringUntil(','); // buffer length
String parse3 = Serial1.readStringUntil(','); // data received from remote Tempf
String parse4 = Serial1.readStringUntil(','); // data received from remote Air Quality PM1.0
String parse5 = Serial1.readStringUntil(','); // data received from remote Latitude
String parse6 = Serial1.readStringUntil(','); // data received from remote Longitude
String parse7 = Serial1.readStringUntil(','); // data received from remote altutude
String parse8 = Serial1.readStringUntil(','); // data received from humidity
String parse9 = Serial1.readStringUntil(','); // data received from remote Air Quality PM2.5
String parse10 = Serial1.readStringUntil('n'); // data received from remote Air Quality PM10.0
if(publishTimer.isTimerReady()) {
if (parse3.length() > 0 && parse4.length() > 0 && parse5.length() > 0) {
// Print only if all parsed variables contain data
Serial.printf("TempF: %s\nAir Quality1: %s\nLatitude: %s\nLongitude: %s\nAltutude: %s\nAQ2: %s\nAQten: %s\n", parse3.c_str(), parse4.c_str(), parse5.c_str(), parse6.c_str(), parse7.c_str(), parse8.c_str(), parse9.c_str(), parse10.c_str());
createEventPayLoad (atof((char *)parse5.c_str()), atof((char *)parse6.c_str()), atof((char *)parse7.c_str())); //json package for GPS
if(mqtt.Update()) {
tempDrone.publish(atof((char *)parse3.c_str()));
airOneDrone.publish(atof((char *)parse4.c_str()));
airTwoDrone.publish(atof((char *)parse9.c_str()));
airTenDrone.publish(atof((char *)parse10.c_str()));
humidityDrone.publish(atof((char *)parse8.c_str()));
//Serial.printf("Publishing %0.2f \n",atof((char *)parse3.c_str()));
}
}
publishTimer.startTimer(PUBTIME);
lastTime = millis();
}
}
//sleepULP(); apparently this is not supported by the Photon2 on serial comm
}
void MQTT_connect() {
int8_t ret;
// Return if already connected.
if (mqtt.connected()) {
return;
}
Serial.print("Connecting to MQTT... ");
while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
Serial.printf("Error Code %s\n",mqtt.connectErrorString(ret));
Serial.printf("Retrying MQTT connection in 5 seconds...\n");
mqtt.disconnect();
delay(5000); // wait 5 seconds and try again
}
Serial.printf("MQTT Connected!\n");
}
bool MQTT_ping() {
static unsigned int last;
bool pingStatus;
if ((millis()-last)>120000) {
Serial.printf("Pinging MQTT \n");
pingStatus = mqtt.ping();
if(!pingStatus) {
Serial.printf("Disconnecting \n");
mqtt.disconnect();
}
last = millis();
}
return pingStatus;
}
void createEventPayLoad (float lat, float lon, float alt) {
JsonWriterStatic <256> jw ;{
JsonWriterAutoObject obj (&jw);
jw.insertKeyValue ("lat", lat);
jw.insertKeyValue ("lon", lon);
jw.insertKeyValue ("alt", alt);
}
if (mqtt.Update()) {
gpsDrone.publish(jw.getBuffer());
}
}
// Configure and Initialize Reyax LoRa module
void reyaxSetup(String password) {
// following four paramaters have most significant effect on range
// recommended within 3 km: 10,7,1,7
// recommended more than 3 km: 12,4,1,7
const int SPREADINGFACTOR = 10;
const int BANDWIDTH = 7;
const int CODINGRATE = 1;
const int PREAMBLE = 7;
String reply; // string to store replies from module
Serial1.printf("AT+ADDRESS=%i\r\n", RADIOADDRESS); // set the radio address
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply from address\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Reply address: %s\n", reply.c_str());
}
Serial1.printf("AT+NETWORKID=%i\r\n", RADIONETWORK); // set the radio network
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply from networkid\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Reply network: %s\n", reply.c_str());
}
Serial1.printf("AT+CPIN=%s\r\n", password.c_str());
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply from password\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Reply: %s\n", reply.c_str());
}
Serial1.printf("AT+PARAMETER=%i,%i,%i,%i\r\n", SPREADINGFACTOR, BANDWIDTH, CODINGRATE, PREAMBLE);
delay(200);
if (Serial1.available() > 0) {
reply = Serial1.readStringUntil('\n');
Serial.printf("reply: %s\n", reply.c_str());
}
Serial1.printf("AT+ADDRESS?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Radio Address: %s\n", reply.c_str());
}
Serial1.printf("AT+NETWORKID?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Radio Network: %s\n", reply.c_str());
}
Serial1.printf("AT+PARAMETER?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("RadioParameters: %s\n", reply.c_str());
}
Serial1.printf("AT+CPIN?\r\n");
delay(200);
if (Serial1.available() > 0) {
Serial.printf("Awaiting Reply\n");
reply = Serial1.readStringUntil('\n');
Serial.printf("Radio Password: %s\n", reply.c_str());
}
}
Comments