요약
이 프로젝트는 파나소닉의 SN-GCJA5 센서로부터 PM2.5 및 PM10 미세 먼지 질량 밀도를 읽을 수 있도록 아두이노 MKR WiFi 1010 보드를 설정하고 , AQI(Air Quality Index, 공기질 지수) 값을 계산한 후 와이파이를 사용하여 AQI 데이터를 Machinechat의 JEDI One IoT 데이터 수집기에 HTTP POST합니다. JEDI One은 라즈베리 파이 4로 구동됩니다.
하드웨어
- RASPBERRY PI 4B/4GB
라즈베리 파이 4 모델 B 4GB SDRAM - MKR WiFi 1010
아두이노 MKR WiFi 1010 평가 기판 - 파나소닉 SN-GCJA5 센서
SN-GC 계열 레이저 타입 미세먼지 (PM) 센서 - JST GHR-05V-S 커넥터 하우징
파나소닉 PM Sensor의 상대 커넥터 - JST AGHGH28K305 접점/점퍼선
JST 커넥터에 사용할 접점이 압착된 점퍼선
소프트웨어
- JEDI One
JEDI One은 바로 사용할 수 있는 IoT 데이터 관리 소프트웨어 솔루션입니다. 기능에는 센서, 장치 및 기계로부터 데이터 수집, 직관적인 실시간 및 과거 데이터와 시스템 뷰 대시보드 구축, 데이터를 모니터링하여 자동으로 데이터 조건에 응답하는 규칙 생성, 이메일과 SMS로 경고 알림 수신 등이 있습니다. - 아두이노
아두이노는 사용하기 쉬운 하드웨어와 소프트웨어에 기반한 오픈 소스 전자 장치 플랫폼입니다.
배경
미국 EPA(Environmental Protection Agency, 환경보호청)에 따르면, “미세먼지는 미세한 고체 또는 액체 입자를 포함하고 있으며, 이는 흡입될 수 있어서 심각한 건강 문제를 야기할 수 있습니다. 직경이 수 마이크로미터 이하인 입자는 폐 깊숙이 침투할 수 있으며 일부는 혈류에까지 침투할 수 있습니다. 이중 직경이 2.5마이크로미터 이하인 입자들은 미세입자 또는 PM2.5라고도 부르며, 건강에 가장 큰 위험을 초래합니다.” 미국 환경보호청의 웹사이트 Particulate Matter (PM) Basics는 미세먼지 오염에 대한 좋은 참고 자료입니다.
파나소닉의 SN-GCJA5 센서는 레이저에 기반한 광학 방식을 사용하여 공기 중의 미세먼지를 감지합니다. 센서는 PM1.0, PM2.5 그리고 PM10 입자에 대한 질량 밀도를 µg/m3 단위로 출력합니다. PM2.5 및 PM10에 대한 AQI 값은 EPA의 기술 문서에 제공된 공식에 기반하여 계산됩니다.
구현
이 프로젝트에서, MKR WiFi 1010은 SN-GCJA5 센서에 전원을 공급하며 통신을 위해 I2C 인터페이스가 연결되어 있습니다. 센서와 통신하여 센서 상태와 데이터를 읽기 위해 깃허브에 있는 Sparkfun의 "SparkFun Particle Sensor SN-GCJA5 Arduino Library"를 프로젝트의 아두이노 코드에 사용하였습니다. 센서의 JST 커넥터와 인터페이스하기 위해 JST의 GHR-05V-S 하우징과 AGHGH28K305 접점/점퍼선을 사용해 커넥터를 제작하였습니다. 전기적 연결은 아래와 같습니다:
MKR WiFi 1010 핀 | SN-GCJA5 센서 커넥터 단자 |
---|---|
5V | 5 VDD |
GND | 4 GND |
12 SCL | 3 SCL |
11 SDA | 2 SDA |
MKR WiFi 1010과 SN-GCJA5 센서 애플리케이션 설정
1 - MKR WiFi 1010에 아두이노를 설정합니다. 링크 참조: Getting started with the MKR WiFi 1010
2 - 애플리케이션에 필요한 라이브러리를 설치합니다. 아두이노의 Library Manager를 통해 아래 라이브러리를 추가합니다:
3 - 코드 검토 (파일명: MKR1010wifiPanSNGCJA5_AQI.ino)
초기 설정 및 와이파이 네트워크에 연결
#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("----------------------------------------");
}
PM2.5 및 PM10에 대한 AQI 계산
/*****
* 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;
}
센서 데이터를 JSON 형식으로 설정하고 HTTP POST를 사용하여 JEDI One으로 전송
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
}
MKR1010wifiPanSNGCJA5_AQI 센서 애플리케이션의 최신 소스 코드는 아래 링크의 깃허브에 있습니다:
JEDI One 설정
1 - Machinechat의 JEDI One이 라즈베리 파이에 아직 설치되지 않은 경우 아래를 참조하십시오:
- JEDI One의 라즈베리 파이 버전인 DK-JEDIONE-RP 구매
- 라즈베리 파이에 설치, Raspberry Pi - Installing JEDI One as a Service를 참조하십시오
2 - JEDI One 대시보드 설정
JEDI One에서, “Dashboards” 탭을 선택한 다음 "+"를 선택하여 새 차트를 추가하고 설정합니다.
차트 이름을 지정하고, "Chart Type"을 선택, "Source"는 MKR1010WiFiSensor_SNGCJA5를 선택, "Property"는 aqiPM2_5와 aqiPM10을 선택, 그리고 "Units"과 "Refresh Interval"을 입력합니다. 완료되면 대시보드는 아래와 비슷하게 보일 것입니다.
결론
파나소닉의 SN-GCJA5 PM 센서와 Sparkfun의 센서 라이브러리 및 MKRWI1010 아두이노 하드웨어 플랫폼을 결합하여 PM 공기질 센서를 빠르고 쉽게 구현할 수 있습니다. 와이파이를 사용하여, 센서 데이터를 라즈베리 파이에서 구동되는 Machinechat의 JEDI One IoT 데이터 관리 소프트웨어에 편리하게 전송할 수 있습니다.
참고 자료
- 파나소닉 - SN-GCJA5 제품 페이지
- 아두이노 - Getting started with the MKR WiFi 1010
- Machinechat의 JEDI One IoT 플랫폼 시작하기
- Sparkfun - SparkFun Particle Sensor SN-GCJA5 Arduino Library
- EPA - 미세먼지 (PM) 오염 웹사이트
영문 원본: Machinechat with Panasonic SN-GCJA5 Air Quality (PM) Sensor