13주차 공공 데이터를 활용한 OLED 디스플레이 - hyunwoo14/SmartDevice GitHub Wiki
- 공공 데이터
- 공공 데이터 활용 신청
- 데이터 미리 보기
- OLED 디스플레이 아이콘 표시
- 아이콘 변환
- 회로
- 코드
- 실행 결과
- OLED 디스플레이 미세먼지 표시
- 코드
- 실행 결과
- 후기
(https://www.data.go.kr/) 공공 데이터 포털에 접속한 뒤 로그인 한다.
"한국환경공단_에어코리아_대기오염정보" Open API 데이터를 활용하겠다.
오픈API에 들어가서 활용 신청 버튼을 클릭한다.
활용 목적을 입력한다.
측정소별 실시간 측정정보 조회 기능을 활용할 것이며 라이선스 표시에 동의한 후 활용신청한다.
인증키는 ESP32에서 활용하게 되므로 복사해서 메모해 둔다.
측정소별 실시간 측정정보 조회 미리보기를 클릭한뒤 위에서 복사해 둔 일반 인증키를 붙여 넣고, 미리보기 버튼을 클릭한다.
우리는 "pm10Value", "dataTime", "pm10Grade"데이터를 사용한다.
"pm10Value"는 미세먼지 농도, "dataTime"은 현재 시간, "pm10Grade"는 좋음, 보통, 나쁨, 매우 나쁨으로 나누어지는 4개 등급이다.
(https:/javl.github.io/image2cpp)에서 아이콘을 변환할 수 있다.
아래는 4개의 미세먼지 등급에 따른 얼굴 표정 아이콘 4종류의 코드이다.
const unsigned char VeryGood [] PROGMEM = {
0x00, 0x0f, 0xf0, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xc0,
0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8,
0x38, 0xc7, 0xe3, 0x1c, 0x70, 0x03, 0xc0, 0x0e, 0x70, 0x03, 0xc0, 0x0e, 0x70, 0x03, 0xc0, 0x0e,
0xf0, 0x03, 0xc0, 0x0f, 0xf8, 0x07, 0xe0, 0x1f, 0xfc, 0x0f, 0xf0, 0x3f, 0xfe, 0x1f, 0xf8, 0x7f,
0xff, 0x3e, 0x7c, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xfd, 0xfe, 0x7f, 0xff,
0x79, 0xff, 0xff, 0x9e, 0x78, 0xff, 0xff, 0x9e, 0x7c, 0x7f, 0xff, 0x1e, 0x3e, 0x3f, 0xfe, 0x3c,
0x1f, 0x1f, 0xfc, 0x78, 0x1f, 0x87, 0xe0, 0xf8, 0x0f, 0xc0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xe0,
0x03, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
const unsigned char Good [] PROGMEM = {
0x00, 0x0f, 0xf0, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0,
0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1e, 0x3f, 0xf8, 0xf8, 0x3e, 0x1f, 0xf8, 0x78,
0x3c, 0x0f, 0xf0, 0x3c, 0x78, 0x87, 0xe2, 0x1e, 0x71, 0xc3, 0xc7, 0x0e, 0x71, 0xe7, 0xc7, 0x9e,
0xfb, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfe, 0x7f, 0xff,
0x79, 0xff, 0xff, 0x9e, 0x78, 0xff, 0xff, 0x9e, 0x7c, 0xff, 0xff, 0x1e, 0x3e, 0x7f, 0xfe, 0x3c,
0x3f, 0x1f, 0xfc, 0x7c, 0x1f, 0x87, 0xf0, 0xf8, 0x0f, 0xc0, 0x01, 0xf0, 0x07, 0xf0, 0x0f, 0xe0,
0x03, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
const unsigned char Bad [] PROGMEM = {
0x00, 0x0f, 0xf0, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0,
0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xf8,
0x3f, 0xff, 0xff, 0xfc, 0x70, 0x03, 0xc0, 0x0e, 0x70, 0x03, 0xc0, 0x0e, 0x70, 0x03, 0xc0, 0x0e,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x3f, 0xff,
0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xe0, 0x07, 0xfe, 0x3f, 0x80, 0x03, 0xfc,
0x3f, 0x0f, 0xf0, 0xfc, 0x1e, 0x3f, 0xfc, 0x78, 0x0f, 0xff, 0xfe, 0xf0, 0x07, 0xff, 0xff, 0xe0,
0x03, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
const unsigned char VeryBad [] PROGMEM = {
0x00, 0x0f, 0xf0, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0,
0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1d, 0xef, 0xf7, 0xb8, 0x38, 0xc7, 0xe3, 0x1c,
0x3c, 0x0f, 0xf0, 0x3c, 0x7e, 0x1f, 0xf8, 0x7e, 0x7e, 0x1f, 0xf8, 0x7e, 0x7c, 0x0f, 0xf0, 0x3e,
0xf8, 0xc7, 0xe3, 0x1f, 0xfd, 0xef, 0xf7, 0xbf, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x3f, 0xff,
0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xe0, 0x07, 0xfe, 0x3f, 0x80, 0x03, 0xfc,
0x3f, 0x0f, 0xf0, 0xfc, 0x1e, 0x3f, 0xfc, 0x78, 0x0f, 0xff, 0xfe, 0xf0, 0x07, 0xff, 0xff, 0xe0,
0x03, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0x80, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
아두이노 editor 창에서 "new tab"을 선택하고 파일 이름을 "images.h"로 정한다.

위의 코드를 images.h파일에 붙여 넣는다.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "images.h" // 이미지 파일을 포함하는 헤더 파일
#define SCREEN_WIDTH 128 // OLED 디스플레이의 너비 (픽셀 단위)
#define SCREEN_HEIGHT 32 // OLED 디스플레이의 높이 (픽셀 단위)
#define OLED_RESET -1 // 리셋 핀 번호 # (아두이노 리셋 핀을 공유하는 경우 -1)
#define SCREEN_ADDRESS 0x3C //128x64의 경우 0x3D, 128x32의 경우 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(115200);
// SSD1306_SWITCHCAPVCC = 내부적으로 3.3V에서 디스플레이 전압 생성
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // OLED초기화 실패시, 더 이상 진행하지 않음
}
// 초기 디스플레이 버퍼 내용을 화면에 표시합니다.
// 라이브러리는 이를 Adafruit 스플래시 화면으로 초기화합니다.
display.display();
delay(2000); // 2초간 대기
display.clearDisplay(); // 디스플레이 지우기
display.drawBitmap(
0,
0,
VeryGood, 32, 32, 1); // VeryGood 이미지를 디스플레이에 그리기
display.drawBitmap(
32,
0,
Good, 32, 32, 1); // Good 이미지를 디스플레이에 그리기
display.drawBitmap(
64,
0,
Bad, 32, 32, 1); // Bad 이미지를 디스플레이에 그리기
display.drawBitmap(
96,
0,
VeryBad, 32, 32, 1); // VeryBad 이미지를 디스플레이에 그리기
display.display();
}
void loop() {
}
회로는 위의 실습과 같다.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <time.h>
#include "images.h" // 이미지 파일을 포함하는 헤더 파일
const char* ssid = "Your_SSID"; // 사용하는 WiFi 네트워크 이름 (SSID)
const char* password = "Your_Password"; // 사용하는 WiFi 네트워크 비밀번호
const int httpPort = 80;
const char* apiKey = "KxUwYnsTwupDGZaymV6gEPF1LS%2BhmuALUZMguQbFDa6ZvuG6O5dy%2BWPgc6%2FJk4deTOnPOosebmo7BxdLthM7pw%3D%3D";
const char* version = "&ver=1.3";
const char* server = "apis.data.go.kr";
const char* stationName = "과천동";
const char* returnType = "xml"; //or json
const char* numOfRows = "1";
const char* pageNo = "1";
const char* dataTerm = "DAILY";
WiFiClient client;
#define SCREEN_WIDTH 128 // OLED 디스플레이의 너비 (픽셀 단위)
#define SCREEN_HEIGHT 32 // OLED 디스플레이의 높이 (픽셀 단위)
#define OLED_RESET -1 // 리셋 핀 번호 # (아두이노 리셋 핀을 공유하는 경우 -1)
#define SCREEN_ADDRESS 0x3C //128x64의 경우 0x3D, 128x32의 경우 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(115200);
// SSD1306_SWITCHCAPVCC = 내부적으로 3.3V에서 디스플레이 전압 생성
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // OLED초기화 실패시, 더 이상 진행하지 않음
}
// 초기 디스플레이 버퍼 내용을 화면에 표시합니다.
// 라이브러리는 이를 Adafruit 스플래시 화면으로 초기화합니다.
display.display();
delay(2000); // 2초간 대기
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("\nConnecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("\nWiFi is connected");
display.clearDisplay(); // 디스플레이 지우기
display.setTextSize(1); // 텍스트 크기를 1로 설정
display.setCursor(0, 0); // 커서 위치를 디스플레이 왼쪽 위 모서리로 설정
display.setTextColor(WHITE); // 텍스트 색상을 흰색으로 설정
display.drawBitmap(
0,
0,
VeryGood, 32, 32, 1); // VeryGood 이미지를 디스플레이에 그리기
display.display();
}
void loop() {
String a[3];
int i = 0;
String dateNtime;
String pm10Val;
String pm10Grade;
String tmp_str;
static int IntervalReq = 1800; // 30분을 초단위로 계산
if (IntervalReq++ > 1800) { //30분 간격으로 data요청
IntervalReq = 0;
Requesthttp();
};
delay(50);
while (client.available()) {
String line = client.readStringUntil('\n');
Serial.println(line);
i = line.indexOf("</dataTime>");
if (i > 0) {
tmp_str = "<dataTime>";
dateNtime = line.substring(line.indexOf(tmp_str) + tmp_str.length(), i);
Serial.println(dateNtime);
}
i = line.indexOf("</pm10Value>");
if (i > 0) {
tmp_str = "<pm10Value>";
pm10Val = line.substring(line.indexOf(tmp_str) + tmp_str.length(), i);
Serial.println(pm10Val);
}
i = line.indexOf("</pm10Grade>");
if (i > 0) {
tmp_str = "<pm10Grade>";
pm10Grade = line.substring(line.indexOf(tmp_str) + tmp_str.length(), i);
Serial.println(pm10Grade);
client.stop();
display.clearDisplay();
displayIcon(atoi(pm10Grade.c_str())); // 등급에 따라 해당 아이콘 그리기
displayString(dateNtime, pm10Val); // 날짜 및 시간과 미세먼지 농도 표시
display.display();
break;
}
}
delay(1000);
}
void Requesthttp() {
if (client.connect(server, httpPort)) {
Serial.println("\nSuccessed connection, and request http protocol");
// HTTP 요청을 보냄
client.print(String("Get /B552584/ArpltnInforInqireSvc"));
client.print(String("/getMsrstnAcctoRltmMesureDnsty?serviceKey="));
client.print(String(apiKey));
client.print(String("&returnType=") + String(returnType));
client.print(String("&numOfRows=") + String(numOfRows));
client.print(String("&pageNo=") + String(pageNo));
// URL 인코딩하여 문자열 전송
client.print(String("&stationName=") + urlencode(String(stationName)));
client.print(String("&dataTerm=") + String(dataTerm));
client.print(String(version));
client.print(String(" HTTP/1.1\r\n"));
client.print(String("Host: ") + String(server) + String("\r\n"));
client.print(String("Connection: close\r\n"));
client.print(String("\r\n\r\n"));
} else {
Serial.println("\nfailed connection");
}
}
void displayString(String dnt, String pmval) {
display.setCursor(50, 3);
display.println(dnt.substring(0, 10)); // 날짜를 디스플레이에 출력
display.setCursor(50, 13);
display.println(dnt.substring(11)); // 시간을 디스플레이에 출력
display.setCursor(50, 23);
display.print(pmval);
display.println(" ug/m^3"); // 미세먼지 농도를 디스플레이에 출력
}
void displayIcon(int grade) {
switch (grade) {
case 1:
display.drawBitmap(
0,
0,
VeryGood, 32, 32, 1); // VeryGood 아이콘을 디스플레이에 그리기
break;
case 2:
display.drawBitmap(
0,
0,
Good, 32, 32, 1); // Good 아이콘을 디스플레이에 그리기
break;
case 3:
display.drawBitmap(
0,
0,
Bad, 32, 32, 1); // Bad 아이콘을 디스플레이에 그리기
break;
case 4:
display.drawBitmap(
0,
0,
VeryBad, 32, 32, 1); // VeryBad 아이콘을 디스플레이에 그리기
break;
}
}
String urlencode(String str) {
String encodedString = "";
char c;
char code0;
char code1;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (c == ' ') {
encodedString += '+';
} else if (isalnum(c)) {
encodedString += c;
} else {
code1 = (c & 0xf) + '0';
if ((c & 0xf) > 9) {
code1 = (c & 0xf) - 10 + 'A';
}
c = (c >> 4) & 0xf;
code0 = c + '0';
if (c > 9) {
code0 = c - 10 + 'A';
}
encodedString += '%';
encodedString += code0;
encodedString += code1;
}
}
return encodedString;
}
공공데이터를 가져와서 ESP32에서 데이터를 받아 미세먼지를 OLED 디스플레이에 나타내는 실습을 해 보았는데, 공공데이터가 미세먼지 뿐만 아니라 다양한 데이터들이 많아서 더 많은 곳에서도 공공데이터를 사용할 수 있겠다고 생각했다. 또 공공데이터 활용 사례에 있는 많은 웹사이트와 모바일앱들을 보면서 다양하게 활용되고 있는 점을 알게 되었다.












