Loading [MathJax]/jax/input/TeX/config.js

2018년 9월 27일 목요일

아두이노 우리지역 미세먼지 알리미 : 3. 환경공단 홈페이지에서 우리동네 미세먼지 데이터 가져오기

아두이노 우리동네 미세먼지 알리미
- 1. MKR1000 + 2.2' TFT LCD(ili9341) : http://woolmakes.blogspot.com/2018/09/1-mkr1000-28-tft-lcdili9341.html
- 2. 아두이노 우리지역 미세먼지 알리미 : 2. MKR1000에 2.2' TFT LCD(ILI9341) + DHT11 : http://woolmakes.blogspot.com/2018/09/2-mkr1000-22-tft-lcdili9341-dht11.html


미세미세 앱을 보면 한국환경공단(에어코리아)에서 제공하는 데이터를 사용함을 알 수 있다.

MKR1000 wifi 아두이노는 wifi를 통해 웹에 접속해 웹 데이터를 불러올 수 있다. 그리고 그 웹 데이터에서 우리에게 필요한 데이터만을 잘라내 사용하면 된다. 이 과정을 파싱(parsing)이라고 한다. 파싱한 데이터를 LCD를 통해 온도, 습도와 함께 나타낸다. 시계도 표시 그러면 끝!





<미세먼지 데이터 사용을 위한 환경공단 API 키 값 받기>

환경공단에서는 개발자들을 위해 미세먼지 데이터를 활용할 수 있도록 제공하는데 이런걸 API라고 한다. 환경공단에서 데이터를 불러와 사용하려면 일단 환경공단에게 우리가 환경공단 데이터를 사용하겠다는 신고(?) 같은걸 해야한다. 그렇게 하면 api 키값을 알려준다. 이 키 값이 있어야 미세먼지 데이터에 접근해 받아올 수 있다. 돈이 들지는 않는다. 다만, 시간과 노력이 조금 필요할 뿐이다.

제일 먼저 공공데이터포털(https://www.data.go.kr) 에 접속해 회원가입을 하자. 빅데이터~ 빅데이터 그랬는데 여기에서 우리나라 공공데이터들을 제공한다. 우와~~~ ㅎㅎ

회원 가입을 하고 "미세먼지"로 데이터를 검색하면 아래쪽에 오픈 API에서 환경공단에서 제공하는 "대기오염정보 조회 서비스"를 선택한다. 그리고 설명 페이지에서 상단에 "활용 신청"을 클릭한다.

활용신청 페이지에 들어가면 이런저런 정보들을 입력하는데 시스템 유형은 "일반", 목적은 "연구", 상세기능정보는 모두 체크한 다음 신청!

그러면 마이페이지 -> 대기오염정보 서비스 -> 일반 인증키 받기를 통해 키를 받는다. 키가 아주 길다. ㅎㅎㅎ 영문자, 숫자, 특수문자가 마구마구 섞여 있다. 이 키를 복사해서 사용하면 된다.

- 전국 관측소들 (2017년 12월 기준)
작년에 교육자료를 만들면서 정리해둔 관측소들이다. 추가나 변경이 되어 있을 수 있다. 우리 동네 근처 미세먼지 관측소를 알고 싶으면 미세미세 앱을 실행해보면 아래쪽에 관측소명이 나온다. 그걸 사용하면 된다.
시도
관측소명
서울
중구종로구용산구광진구성동구중랑구동대문구성북구도봉구은평구서대문구마포구강서구구로구영등포구동작구관악구강남구서초구송파구강동구금천구강북구양천구노원구
부산
광복동태종대전포동명장동대연동학장동덕천동청룡동좌동장림동대저동녹산동연산동기장읍용수리수정동부곡동광안동대신동
대구
수창동지산동서호동이현동대명동노원동신암동태전동만촌동호림동현풍면
인천
신흥송림구월동숭의부평연희검단계산고잔석남송해동춘운서논현원당
광주
서석동농성동두암동건국동송정1오선동주월동
대전
읍내동문평동문창동구성동노은동성남동1, 정림동둔산동
울산
대송동성남동부곡동(울산), 여천동(울산), 야음동삼산동신정동덕산리무거동효문동화산리상남리농소동삼남면약사동
경기
신풍동인계동광교동영통동천천동고색동단대동정자동수내동복정동운중동상대원동의정부동의정부1안양6부림동호계동안양2철산동소하동고잔동원시동본오동원곡동부곡동1, 대부동호수동별양동과천동교문동동구동부곡3고천동정왕동시화산단대야동금곡동오남읍비전동안중평택항금촌동운정행신동식사동신원동경안동김량장동수지기흥창전동선단동사우동고촌읍통진읍산본동오산동신장동남양읍향남동탄백석읍보산동봉산동여주연천가평양평소사본동내동2오정동
강원
중앙로석사동중앙동(원주), 명륜동옥천동천곡동남양동1
충북
송정동(봉명동), 사천동문화동용암동호암동칠금동장락동오창읍매포읍진천읍옥천읍
충남
성황동백석동공주부여읍독곶리동문동난지도리정곡리모종동논산이원면태안읍예산군대천2홍성읍
전북
중앙동(전주), 삼천동팔복동신풍동(군산), 소룡동개정동남중동팔봉동모현동연지동죽항동고창읍부안군요촌동
전남
용당동부흥동광무동월내동문수동여천동(여수), 덕충동장천동연향동순천만호두리중동태인동진상면광양읍나불리
경북
장흥동장량동대도동대송면, 3공단성건동문당동남문동공단동원평동형곡동, 4공단휴천동중방동
경남
회원동봉암동상봉동대안동상대동명서동웅남동가음정동용지동사파동경화동하동읍동상동삼방동장유동아주동사천읍북부동웅상읍내일동무전동
제주
이도동연동동홍동성산읍
세종
신흥동아름동


* 참고로 일일 트래픽이 제한되어 있다. 그래서 몇 번 테스트 하다보면 일일트래픽 제한에 걸려 막힌다. (ㅠㅠ) 트래픽 용량 추가 신청은 각자 알아서 ^^ 학교에서 활용할 경우에는 교육용으로 활용하려고 한다고 간단한 자료를 만들어 함께 신청하면 용량을 늘려준다. 친절하다.

그리고 신청하더라도 바로 서비스를 이용할 수는 없다. 하루 정도 기다려야 했다. 2017년 말 기준이다. 지금은 어떤지 잘 모르겠다. 난 이미 신청이 되어 있기때문에...


<MKR1000 wifi 접속해 웹 데이터 불러오기>
따로 회로는 필요없다. MKR1000과 wifi 공유기만 있으면 된다. 없으면 스마트폰 테더링 ^^
아두이노 IDE 예제에서 WIFI101 -> WifiWebClient 예제를 불러온다.

사용하는 WIFI의 SSID와 비밀번호를 입력하면 된다.
예제를 불러오면 arduino_secrets.h 탭이 있고 여기에 입력하면 된다.

아니면 WifiWebClient 코드에 바로 입력해도 된다.

char ssid[] = "U+123154"; 
char pass[] = "12345678";

그러고 나서 컴파일하고 업로드한 후에 시리얼 모니터를 실행해보자.
잘 작동이 되면 다음 그림과 같은 화면이 뜬다. 잘 된다.





이제 이 코드를 그대로 수정해서 에어코리아에서 미세먼지 값을 가져와보자.

#include <SPI.h>
#include <WiFi101.h>
#include "arduino_secrets.h" 

//// 아래 3줄을 추가하고 API키값은 공공데이터포털에 값을 복사해서 붙여넣기
//// 이 APIKEY는 작동하지 않는 키 값임
#define APIKEY    "S7wzLtVFLILOfnpEs%2B2QIUSVhqQoZ6udu8Eil%2FNLlWOP4UfsTvE%2B4DJQaZyjekaA%3D%3D"
#define CITY    "종로구"
#define VERSION    "1.3"

char ssid[] = "ssid";   
char pass[] = "pass";  
int keyIndex = 0; 

int status = WL_IDLE_STATUS;

///// 서버 주소를 airkorea로 변경
char server[] = "openapi.airkorea.or.kr";

WiFiClient client;

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; 
  }

  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    while (true);
  }

  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);

    delay(10000);
  }
  Serial.println("Connected to wifi");
  printWiFiStatus();

  Serial.println("\nStarting connection to server...");

  if (client.connect(server, 80)) {
    Serial.println("connected to server");
    
    // 아래 11줄을 해당 위치에 바꿔 넣는다 
    client.print(F("GET /openapi/services/rest/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty?stationName="));
    client.print(CITY);
    client.print(F("&dataTerm=daily&pageNo=1&numOfRows=1&ServiceKey="));
    client.print(APIKEY);
    client.print(F("&ver="));
    client.print(VERSION);
    client.print(F(" HTTP/1.1\r\n"));
    client.println("Host: openapi.airkorea.or.kr");
    client.println("User-Agent: ArduinoWiFi/1.1");
    client.println("Connection: close");
    client.println();
  }
}

void loop() {
  while (client.available()) {
    char c = client.read();
    Serial.write(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting from server.");
    client.stop();

    while (true);
  }
}


void printWiFiStatus() {
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}


역시 컴파일하고 업로드한 다음에 시리얼 모니터를 실행하면 다음과 같이 뜬다.




html 코드가 바로 보인다. ㅎㅎ 저 html 태그들 중에 pm10Value와 pm25Value를 잘라서 가져오면 된다. 이 과정을 파싱이라고 한다. 파싱하는 부분은 다음 사이트를 직접 참고했다. https://blog.naver.com/compass1111/221143787550

  int i = 0;
  while (client.available()) {
    //char c = client.read();
    //Serial.write(c);
    i++;
    String line = client.readStringUntil('\n');

    int datatime= line.indexOf("</dataTime>");
    
    if(datatime>0){
      String tmp_str="<dataTime>";
      String wt_datatime = line.substring(line.indexOf(tmp_str)+tmp_str.length(),datatime);
      Serial.print("datetime : ");
      Serial.println(wt_datatime);
    }
    
    int pm10value= line.indexOf("</pm10Value>");
    
    if(pm10value>0){
      String tmp_str="<pm10Value>";
      String wt_pm10value = line.substring(line.indexOf(tmp_str)+tmp_str.length(),pm10value);
      Serial.print("pm10 value : ");
      Serial.println(wt_pm10value);
    }

    int pm25value= line.indexOf("</pm25Value>");
    
    if(pm25value>0){
      String tmp_str="<pm25Value>";
      String wt_pm25value = line.substring(line.indexOf(tmp_str)+tmp_str.length(),pm25value);
      Serial.print("pm25 value : ");
      Serial.println(wt_pm25value);
    }
    
  }

loop문 ->  while문 위아래를 위와 같이 바꿔준다.

코드를 넣고 실행하면 시리얼 모니터에 다음과 같이 나온다.





자 이제 마지막으로 주기적으로 데이터를 불러오도록 해야한다. WifiWebClient_repeating이란 예제가 있다. 이 예제를 실행하면 아두이노 홈페이지 데이터를 잘 불러온다. 근데 에어코리아 데이터는 두 번 로딩한뒤 그 뒤로는 데이터를 가져오지 못했다..... 왜 그런지 나도 잘 모르겠다. 위에 참고한 사이트 분에게 질문을 남겨도 안그랬던거 같다.

흐음... 그래서 내가 선택한 방법은 wifi 접속도 새로 하는거다. 에어코리아 데이터 자체가 1시간을 주기로 업데이트 되기 때문에 wifi 에 계속 접속해서 데이터를 불러올 필요가 없다. 그래서 wifi 접속을 끊었다가 새로 접속하는 방법으로 실행해보니 데이터를 잘 불러온다. ㅎㅎㅎ 뭔가 꼼수같지만.. 적당히 잘 되니 그대로 사용하기로 한다.

그리고 DHT11을 붙였다. 또, 시계도 띄웠다. MKR1000에는 RTC가 내장되어 있어 별도의 RTC모듈 없이 시계로 사용할 수 있다. 미세먼지 농도에 따라 LCD 배경 색이 바뀐다.


#include <SPI.h>
#include <WiFi101.h>
#include <WiFiUdp.h>
#include <RTCZero.h>
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <DHT.h>
#include <Wire.h>

RTCZero rtc;
WiFiClient client;

///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "wool2";        // your network SSID (name)
char pass[] = "";    // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;            // your network key Index number (needed only for WEP)

int status = WL_IDLE_STATUS;

char server[] = "openapi.airkorea.or.kr";

#define APIKEY    "wzLtVFLILOfnpEs%2BQoZ6udu8Eil%2FNLlWOP4UfsTvE%2B4DJQaZyjekaA%3D%3D"
#define CITY    "신풍동"//"신풍동"
#define VERSION    "1.3"

const int reconnect_wifi_Interval = 3; // 10 minutes
const long refresh_lcd_Interval = 20L * 1000L; // 20 seconds

const int GMT = 9; //change this to adapt it to your time zone

long count = 0;

// For MKR1000
#define TFT_MISO 10 // SDO/MISO
#define TFT_CLK 9 // SCK
#define TFT_MOSI 8 // SDI/MOSI
#define TFT_DC 7 // DC/RS
#define TFT_CS 6 // CS 
#define TFT_RST 5 // RESET
//LED - vcc(3.3V)

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);

#define DHTPIN 2    
#define DHTTYPE DHT11
DHT dht(DHTPIN,DHTTYPE);

String tft_datatime = "";
String tft_pm10value = "";
String tft_pm25value = "";

int pm10 = 0;
int pm25 = 0;

int time_h = 0;
int time_m = 0;

void setup() {
  //Initialize serial and wait for port to open:
  Serial.begin(9600);

  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);
  }

  rtc.begin();
  
  wificonnent();

  dustRequest();
  
  /////// tft lcd ///////////////////
  tft.begin();

  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); 
  
  Serial.println(F("Benchmark                Time (microseconds)"));
  delay(10);
  Serial.println(F("Screen fill              "));
  //Serial.println(testFillScreen());
  delay(500);
}

void loop() {

  time_h = rtc.getHours() + GMT;
  time_m = rtc.getMinutes();

  if(time_h >24){
    time_h = time_h - 24;
  }
  Serial.print(time_h);
  Serial.print(":");
  Serial.println(time_m);

  if(time_m % reconnect_wifi_Interval == 0) {
        
    wificonnent();

    dustRequest();

    delay(30000);
    
  }
  
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float f = dht.readTemperature(true);

  pm10 = tft_pm10value.toInt();
  pm25 = tft_pm25value.toInt();

  if(pm10 > 100 || pm25 > 50) {
    tft.fillScreen(ILI9341_RED);
  }
  else if(pm10 > 50 || pm25 > 25) {
    tft.fillScreen(ILI9341_ORANGE);
  }
  else if(pm10 > 30 || pm25 > 15) {
    tft.fillScreen(ILI9341_GREEN);
  }
  else {
    tft.fillScreen(ILI9341_BLUE);
  }
  
  tft.setRotation(0); //0~3
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_WHITE);

  tft.setTextSize(2);
  tft.println(" ");
  
  tft.setTextSize(8);
  if(time_h < 10) {
    tft.print("0");
  }
  tft.print(time_h);
  tft.print(":");
  if(time_m < 10) {
    tft.print("0");
  }
  tft.println(time_m);

  tft.setTextSize(2);
  tft.println(" ");
  
  tft.setTextSize(5);
  
  tft.print("H: ");
  tft.print(int(h));
  tft.println("%");
  tft.print("T: ");
  tft.print(int(t));
  tft.println("*C ");

  tft.setTextSize(2);
  tft.println(" ");
  
  tft.setTextSize(4);
  tft.print("pm10:");
  tft.println(pm10);
  tft.print("pm2.5:");
  tft.println(pm25);

  tft.setTextSize(1);
  tft.print("update: ");
  tft.println(tft_datatime);

  tft.print("refresh count: ");
  tft.println(count);
  tft.print(" made by wool");

  //end and disconnect wifi
  client.stop();
  WiFi.end();

  status = WiFi.status();
    
  delay(refresh_lcd_Interval);

  Serial.println("--------------------------------------");

}

void wificonnent() {
  
  // attempt to connect to WiFi network:
  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:
    delay(10000);
  }
  // you're connected now, so print out the status:
  printWiFiStatus();

  get_time();
  
}


// this method makes a HTTP connection to the server:
void dustRequest() {
  // close any connection before send a new request.
  // This will free the socket on the WiFi shield
  client.stop();

  // if there's a successful connection:
  if (client.connect(server, 80)) {
    Serial.println("connecting...");
    // send the HTTP PUT request:
    client.print(F("GET /openapi/services/rest/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty?stationName="));
    client.print(CITY);
    client.print(F("&dataTerm=daily&pageNo=1&numOfRows=1&ServiceKey="));
    client.print(APIKEY);
    client.print(F("&ver="));
    client.print(VERSION);
    client.print(F(" HTTP/1.1\r\n"));
    client.println("Host: openapi.airkorea.or.kr");
    client.println("Connection: close");
    client.println();

    // note the time that the connection was made:
    //lastConnectionTime = millis();
  }
  else {
    // if you couldn't make a connection:
    Serial.println("connection failed");
  }

  while(!client.available()) {
  }

  int i = 0;
  while (client.available()) {
    i++;
    String line = client.readStringUntil('\n');

    int datatime= line.indexOf("</dataTime>");
    
    if(datatime>0){
      Serial.println("-----------------------------");
      String tmp_str="<dataTime>";
      String wt_datatime = line.substring(line.indexOf(tmp_str)+tmp_str.length(),datatime);
      Serial.print("datetime : ");
      Serial.println(wt_datatime);
      tft_datatime = wt_datatime;
    }
    
    int pm10value= line.indexOf("</pm10Value>");
    
    if(pm10value>0){
      String tmp_str="<pm10Value>";
      String wt_pm10value = line.substring(line.indexOf(tmp_str)+tmp_str.length(),pm10value);
      Serial.print("pm10 value : ");
      Serial.println(wt_pm10value);
      tft_pm10value = wt_pm10value;
    }

    int pm25value= line.indexOf("</pm25Value>");
    
    if(pm25value>0){
      String tmp_str="<pm25Value>";
      String wt_pm25value = line.substring(line.indexOf(tmp_str)+tmp_str.length(),pm25value);
      Serial.print("pm25 value : ");
      Serial.println(wt_pm25value);
      count++;
      Serial.println(count);
      tft_pm25value = wt_pm25value;
    }
    
  }
}

void printWiFiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void get_time() {
  
  unsigned long epoch;
  int numberOfTries = 0, maxTries = 6;
  do {
    epoch = WiFi.getTime();
    numberOfTries++;
  }
  while ((epoch == 0) && (numberOfTries < maxTries));

  if (numberOfTries > maxTries) {
    Serial.print("NTP unreachable!!");
    while (1);
  }
  else {
    Serial.print("Epoch received: ");
    Serial.println(epoch);
    rtc.setEpoch(epoch);

    Serial.println();
  }
}


<참고>
1. 코코아팹 https://kocoafab.cc/tutorial/view/101
2. arduino.cc https://www.arduino.cc/en/Reference/SPI
3. 만드는 놀이터 https://blog.naver.com/compass1111/221075756703
4. arduino.cc https://www.arduino.cc/en/Tutorial/WiFiRTC

댓글 없음:

댓글 쓰기