- 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
댓글 없음:
댓글 쓰기