Machinechat with Panasonic SN-GCJA5 Air Quality (PM) Sensor

Description

This project sets up an Arduino MKRWIFI1010 board to read PM2.5 and PM10 particulate matter mass density values from a Panasonic SN-GCJA5 sensor, calculates AQI (Air Quality Index) values and uses WiFi to HTTP post the AQI data to machinechat’s JEDI One IoT data collector. JEDI One is running on a Raspberry Pi 4.

image

image

Hardware

Software

  • JEDI One
    JEDI One is a ready-to-use IoT data management software solution. Capabilities include: collect data from sensors, devices and machines; build intuitive real-time and historical data and system view dashboards; create rules to monitor and respond to data conditions automatically; receive alert notifications by email and SMS.
  • Arduino
    Arduino is an open-source electronics platform based on easy-to-use hardware and software.

Background

Per the United States EPA (Environmental Protection Agency): “Particulate matter contains microscopic solids or liquid droplets that are so small that they can be inhaled and cause serious health problems. Some particles less than 10 micrometers in diameter can get deep into your lungs and some may even get into your bloodstream. Of these, particles less than 2.5 micrometers in diameter, also known as fine particles or PM2.5, pose the greatest risk to health.” A good reference to Particle pollution is the US Environmental Protection Agency’s website: Particulate Matter (PM) Basics | US EPA .
Panasonic’s SN-GCJA5 sensor uses a laser based optical method to detect particulate matter in the air. The sensor outputs mass-density values for PM1.0, Pm2.5 and PM10 particles in µg/m3. AQI (Air Quality Index) values for PM2.5 and PM10 are calculated based on the formula provided in the EPA technical document https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf .

Implementation

For this project, the MKRWIFI1010 provides power to the SN-GCJA5 sensor and is wired to it’s I2C interface for communication. Sparkfun’s “SparkFun Particle Sensor SN-GCJA5 Arduino Library” on github is used by the project’s Arduino code to communicate with the sensor to read sensor status and data. A connector using a JST GHR-05V-S housing and AGHGH28K305 contact/jumper leads was built up to interface to the JST connector on the sensor. Electrical connections are shown below:

MKRWIFI1010 pin SN-GCJA5 sensor connector terminals
5V 5 VDD
GND 4 GND
12 SCL 3 SCL
11 SDA 2 SDA

Set up the MKRWIFI1010 and SN-GCJA5 sensor application

1 - Set up Arduino on the MKRWIFI1010. See link Getting started with the MKR WiFi 1010

2 - Install libraries needed for application. Add these libraries thru Arduino’s Library Manager:

3 - Code walkthrough (filename: MKR1010wifiPanSNGCJA5_AQI.ino)

Initial setup and connect to Wifi network

#include <Arduino.h>
#include <WiFiNINA.h>
#include <ArduinoHttpClient.h>
#include "arduino_secrets.h" 
#include <Wire.h>

#include <ArduinoJson.h>   //include Json library

#include "SparkFun_Particle_Sensor_SN-GCJA5_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_Particle_SN-GCJA5

// Create a unique ID for the data from the MKRWIFI1010 running this code
const char* jediID = "MKR1010WiFiSensor_SNGCJA5";

///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID;        // your network SSID (name)
char pass[] = SECRET_PASS;    // your network password (use for WPA, or use as key for WEP)
int status = WL_IDLE_STATUS;     // the Wifi radio's status

// IP address of server or Raspberry Pi running Machinechat JEDI software
// If you changed the JEDI port number, replace 8100 with the new port
char serverAddress[] = "192.168.1.7";  // server address
int port = 8100;

WiFiClient client;
HttpClient http = HttpClient(client, serverAddress, port);


SFE_PARTICLE_SENSOR myAirSensor;

float pm2_5Avg = 0.0;  //determine 10 second PM2.5 average
float pm10Avg = 0.0;  //determine 10 second PM2.5 average
float AQI2_5;
float AQI10;
int loop_ctr = 0;

void setup()
{
  Serial.begin(9600);
  Serial.println("begin...");  
  Serial.println(F("Panasonic SN-GCJA5 Example"));

  Wire.begin();

  if (myAirSensor.begin() == false)
  {
    Serial.println("The particle sensor did not respond. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Sensor started");
  Serial.println("PM:1.0, 2.5, 10, Counts: 0.5, 1, 2.5, 5, 7.5, 10,");

  // attempt to connect to Wifi network:
    Serial.print("Attempting to connect to network: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network:
    status = WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // you're connected now, so print out the data:
  Serial.println("You're connected to the network");
  Serial.println("----------------------------------------");
  printData();
  Serial.println("----------------------------------------");
}

Calculate AQI for PM2.5 and PM10

/*****
 * purpose: calculate PM2.5 AQI
 * parameters:
 *    float Avg2_5   average PM2.5
 *    
 * return value:
 *    float AQI       PM2.5 AQI
 ****/
float CalcAQI25(float Avg2_5)
 {
  float AQI;
  if (Avg2_5 < 12.0) {
    AQI = 4.17 * Avg2_5;  //calculation for PM2.5 0.0 to 12.0 "Good"
  } else if (Avg2_5 < 35.4) {
    AQI = (2.10 * (Avg2_5 - 12.1)) + 51;  //calculation for PM2.5 12.1 to 35.4 "Moderate"  
  } else if (Avg2_5 < 55.4) {
    AQI = (2.46 * (Avg2_5 - 35.5)) + 101;   //calculation for PM2.5 35.5 to 55.4 "Unhealthy for Sensitive Groups"
  } else if (Avg2_5 < 150.4) {
    AQI = (0.52 * (Avg2_5 - 55.5)) + 151;  //calculation for PM2.5 55.5 to 150.4 "Unhealthy"
  } else if (Avg2_5 < 250.4) {
    AQI = (0.99 * (Avg2_5 - 150.5)) + 201;  //calculation for PM2.5 150.5 to 250.4 "Very Unhealthy"
  } else if (Avg2_5 < 350.4) {
    AQI = (0.99 * (Avg2_5 - 250.5)) + 301; //calculation for PM2.5 250.5 to 350.4 "Hazardous"
  } else if (Avg2_5 < 500.4) {
    AQI = (0.66 * (Avg2_5 - 350.5)) + 401; //calculation for PM2.5 350.5 to 500.4 "Hazardous"
  } else {
    AQI = 501.0;
  }
  return AQI;
 }

 /*****
 * purpose: calculate PM10 AQI
 * parameters:
 *    float Avg10   average PM2.5
 *    
 * return value:
 *    float AQI       PM10 AQI
 ****/
float CalcAQI10(float Avg10)
 {
  float AQI;
  if (Avg10 < 54.0) {
    AQI = 0.93 * Avg10;  //calculation for PM10 0.0 to 54.0 "Good"
  } else if (Avg10 < 154) {
    AQI = (0.49 * (Avg10 - 55)) + 51;  //calculation for PM10 55 to 154 "Moderate"  
  } else if (Avg10 < 254) {
    AQI = (0.49 * (Avg10 - 155)) + 101;   //calculation for PM10 155 to 254 "Unhealthy for Sensitive Groups"
  } else if (Avg10 < 354) {
    AQI = (0.49 * (Avg10 - 255)) + 151;  //calculation for PM10 255 to 354 "Unhealthy"
  } else if (Avg10 < 424) {
    AQI = (1.43 * (Avg10 - 355)) + 201;  //calculation for PM10 355 to 424 "Very Unhealthy"
  } else if (Avg10 < 504) {
    AQI = (1.25 * (Avg10 - 425)) + 301; //calculation for PM10 425 to 504 "Hazardous"
  } else if (Avg10 < 604) {
    AQI = (1.0 * (Avg10 - 505)) + 401; //calculation for PM10 505 to 604 "Hazardous"
  } else {
    AQI = 501.0;
  }
  return AQI;
 }

Set up sensor data in JSON format and send to JEDI One using HTTP post

void loop()
{
  // prep for sending HTTP post to JEDI One
  String postData1; //Json string 

  // get particle sensor data
  float pm1_0 = myAirSensor.getPM1_0();
  Serial.print(pm1_0, 2); //Print float with 2 decimals
  Serial.print(",");

  float pm2_5 = myAirSensor.getPM2_5();
  pm2_5Avg = pm2_5Avg + pm2_5;
  Serial.print(pm2_5, 2);
  Serial.print(",");

  float pm10 = myAirSensor.getPM10();
  pm10Avg = pm10Avg + pm10;
  Serial.print(pm10, 2);
  Serial.print(",");

  unsigned int pc0_5 = myAirSensor.getPC0_5();
  Serial.print(pc0_5);
  Serial.print(",");

  unsigned int pc1_0 = myAirSensor.getPC1_0();
  Serial.print(pc1_0);
  Serial.print(",");

  unsigned int pc2_5 = myAirSensor.getPC2_5();
  Serial.print(pc2_5);
  Serial.print(",");

  unsigned int pc5_0 = myAirSensor.getPC5_0();
  Serial.print(pc5_0);
  Serial.print(",");

  unsigned int pc7_5 = myAirSensor.getPC7_5();
  Serial.print(pc7_5);
  Serial.print(",");

  unsigned int pc10 = myAirSensor.getPC10();
  Serial.print(pc10);
  Serial.print(",");

  Serial.println();

 loop_ctr = loop_ctr + 1;
 Serial.print("loop_ctr = ");
 Serial.println(loop_ctr);
 if (loop_ctr > 9)  {
   Serial.print("pm2_5Avg = ");
   pm2_5Avg = pm2_5Avg/10;
   Serial.print(pm2_5Avg);
   Serial.print("   pm10Avg = ");
   pm10Avg = pm10Avg/10;
   Serial.println(pm10Avg);
   AQI2_5 = CalcAQI25(pm2_5Avg);   
   Serial.print("AQI PM2.5 = ");
   Serial.println(AQI2_5);
   AQI10 = CalcAQI10(pm10Avg);   
   Serial.print("AQI PM10 = ");
   Serial.println(AQI10);  
   // round and create int version of AQI10
   AQI10 = AQI10 + 0.5;
   int AQI10int = (int) AQI10;
   Serial.println(AQI10int); 

    //Following code creates the serialized JSON string to send to JEDI One
    //using ArduinoJson library
    StaticJsonDocument <200> doc;
    JsonObject context = doc.createNestedObject("context");
    context["target_id"] = String(jediID);
    JsonObject data = doc.createNestedObject("data");
    data["aqiPM2_5"] = AQI2_5;
    data["aqiPM10"] = AQI10int;
    serializeJson(doc, postData1);
    Serial.println(postData1);    //debug, can be commented out

    //HTTP post code start
    if (WiFi.status() == WL_CONNECTED) {
      //format http post to be compatible with JEDI one
      String contentType = "application/json";
      http.post("/v1/data/mc", contentType, postData1);
      // read the status code and body of the response
      int statusCode = http.responseStatusCode();
      String response = http.responseBody();
      Serial.print("Status code: ");
      Serial.println(statusCode);
      Serial.print("Response: ");
      Serial.println(response);
   } else {
      Serial.println("Error in WiFi connection");
   }


   // reset PM averages and loop counter 
   pm2_5Avg = 0;
   pm10Avg = 0;
   loop_ctr = 0;
 }   

  delay(1000); //The sensor has new data every second
}

Latest source code for the MKR1010wifiPanSNGCJA5_AQI sensor application is on github at below link:

Set up the JEDI One

1 - If machinechat JEDI One is not already installed on the Raspberry Pi see below:

2 - Set up the JEDI One dasboard

In the JEDI One, select “Dashboards” tab, then select “+” to add a new chart and configure.

Name the chart, select “Chart Type”, select “Source” (MKR1010WiFiSensor_SNGCJA5), select "Property (aqiPM2_5 and aqiPM10), enter “Units” and enter “Refresh Interval”. When complete the dashboard should liook similar to below.

Conclusion

Panasonic’s SN-GCJA5 PM sensor combined with Sparkfun’s sensor library and MKRWIFI1010 Arduino hardware platforms makes it quick and easy to implement a PM air quality sensor. Using WiFi, the sensor data can easily be sent to machinechat’s JEDI One IoT data management software running on a Raspberry Pi.

References