13주차 ‐ 공공데이터 활용 - movie-01/SmartDevice GitHub Wiki
- API
- 실습 1
- 실습 2
API(응용 프로그램 프로그래밍 인터페이스, Application Programming Interface)는 서로 다른 소프트웨어 간의 상호작용을 가능하게 해주는 인터페이스다. 쉽게 말해, API는 프로그램이 다른 프로그램의 기능을 사용할 수 있게 만들어주는 약속된 규칙이자 통신 창구다.
- 기능: 한 시스템이 다른 시스템의 기능을 직접 접근하지 않고, 정해진 형식으로 요청을 보내면 그에 맞는 응답을 돌려주는 구조.
- 형식: REST API, GraphQL, gRPC 등 다양한 방식이 존재.
- 역할 예시:
- 날씨 정보를 보여주는 앱 → 기상청 API 호출
- 로그인 시 Google/Facebook 연동 → OAuth API 사용
분야 | 활용 사례 |
---|---|
웹 개발 | 외부 결제 시스템 연동 (카카오페이, 토스 등) |
앱 개발 | Google Maps API를 활용한 위치 기반 서비스 |
데이터 분석 | 공공데이터 포털 API를 이용한 실시간 통계 수집 |
클라우드 | AWS, Azure API로 서버 관리 자동화 |
금융 | 증권 API로 주식 정보/거래 자동화 (예: 키움 Open API) |
장점 | 설명 |
---|---|
재사용성 | 기존 시스템의 기능을 다시 만들 필요 없이 API만 호출하면 됨 |
유연성 | 다양한 클라이언트(모바일, 웹 등)에서 동일한 API 활용 가능 |
표준화 | 일정한 호출 방식과 응답 형식으로 개발 일관성 유지 |
보안성 | 직접 접근 없이 인증된 접근만 허용 가능 (OAuth, API Key 등) |
단점 | 설명 |
---|---|
의존성 | 외부 API가 변경되거나 중단되면 시스템 전체에 영향 |
속도 제한 | 호출 횟수 제한 (Rate Limit)으로 인해 대량 요청 어려움 |
보안 취약점 | 인증 정보 노출 시 보안 위협 가능성 존재 |
디버깅 어려움 | 응답 지연, 오류 원인을 외부 시스템에서 알기 어려움 |
- 한국 공공데이터포털 API:
- 전국 대기질, 미세먼지, 인구 통계 등 실시간 데이터 수집
- 공공데이터포털: https://www.data.go.kr
- OpenAI GPT API:
- 기업들이 고객상담 챗봇, 이메일 요약, 회의록 자동화 등에 도입
- → 예: 삼성, 네이버, LG CNS 등 국내 대기업들 사용 확대
- 증권사 API (키움, 미래에셋):
- 개인 투자자들이 자동매매 봇 개발, 백테스팅 등에 활용
- → 키움증권 Open API+, 한국투자증권 REST API
- Instagram, TikTok API:
- 쇼핑몰에서 상품 연동, 해시태그 기반 리뷰 크롤링 자동화 등에 사용
- 준비물
- ESP32 보드
- ESP32 확장 실드
- OLED 디스플레이 (128x32)
- "https://javl.github.io/image2cpp/" 사이트에 접속하고, 실습에 필요한 32x32 사진을 준비한다.
- 파일을 선택하고, 아래와 같이 준비하고 "Generate code" 실행한다
- 아두이노에서 "img.h" 파일을 생성하고 각 코드를 입력한다.
// 'happy', 32x32px
const unsigned char epd_bitmap_happy [] 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
};
// 'smile', 32x32px
const unsigned char epd_bitmap_smile [] 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, 0x1e, 0x3f, 0xf8, 0xf8, 0x1e, 0x1f, 0xf8, 0x78,
0x3c, 0x0f, 0xf0, 0x3c, 0x78, 0x87, 0xe2, 0x1e, 0x70, 0xc3, 0xc3, 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, 0x7f, 0xff, 0x1e, 0x3e, 0x3f, 0xfe, 0x3c,
0x1e, 0x1f, 0xfc, 0x78, 0x1f, 0x87, 0xf0, 0xf8, 0x0f, 0xc0, 0x01, 0xf0, 0x07, 0xf0, 0x07, 0xe0,
0x03, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
// 'notGood', 32x32px
const unsigned char epd_bitmap_notGood [] 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,
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, 0x7f, 0xfe, 0x7f, 0xe0, 0x07, 0xfe, 0x3f, 0x80, 0x03, 0xfc,
0x1f, 0x0f, 0xf0, 0xf8, 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
};
// 'bad', 32x32px
const unsigned char epd_bitmap_bad [] 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, 0x1d, 0xef, 0xf7, 0xb8, 0x18, 0xc7, 0xe3, 0x18,
0x3c, 0x07, 0xf0, 0x1c, 0x7e, 0x1f, 0xf8, 0x7e, 0x7e, 0x1f, 0xf8, 0x7e, 0x7c, 0x07, 0xf0, 0x1e,
0xf8, 0x07, 0xe0, 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, 0x7f, 0xfe, 0x7f, 0xe0, 0x07, 0xfe, 0x3f, 0x80, 0x01, 0xfc,
0x1f, 0x0f, 0xf0, 0xf8, 0x1e, 0x3f, 0xfc, 0x78, 0x0f, 0x7f, 0xfe, 0xf0, 0x07, 0xff, 0xff, 0xe0,
0x03, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 576)
const int epd_bitmap_allArray_LEN = 4;
const unsigned char* epd_bitmap_allArray[4] = {
epd_bitmap_bad,
epd_bitmap_happy,
epd_bitmap_notGood,
epd_bitmap_smile
};
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "img.h" // 이미지 파일을 포함하는 헤더 파일
#define happy epd_bitmap_happy
#define smile epd_bitmap_smile
#define notGood epd_bitmap_notGood
#define bad epd_bitmap_bad
#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,
happy, 32, 32, 1); // happy 이미지를 디스플레이에 그리기
display.drawBitmap(
32,
0,
smile, 32, 32, 1); // smile 이미지를 디스플레이에 그리기
display.drawBitmap(
64,
0,
notGood, 32, 32, 1); // notGood 이미지를 디스플레이에 그리기
display.drawBitmap(
96,
0,
bad, 32, 32, 1); // bad 이미지를 디스플레이에 그리기
display.display();
}
void loop() {
}
- 공공데이터 포털에 접속 후 회원 가입 및 로그인을 한다
- 검색 창에 “한국환경공단_에어코리아_대기오염정보” 입력 후 아래 사진과 같이 Open API 데이터를 찾아 "활용 신청" 클릭
- 활용 목적을 입력하고 라이센스 표기 동의 후 -> 측정소별 실시 간 측정정보 조회를 활용
- 승인이 완료 후 -> 일반 인증키 발급
- 아래 사진 참고하여 코드 작성
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <time.h>
#include "img.h" // 비트맵 이미지가 정의된 헤더
const char* ssid = "Your_SSID"; // 사용하는 WiFi 네트워크 이름 (SSID)
const char* password = "Your_Password"; // 사용하는 WiFi 네트워크 비밀번호
const int httpPort = 80;
const char* apiKey = "인증키(URL Encode)"; // 인증키 복사 붙여놓
const char* version = "&ver=1.3";
const char* server = "apis.data.go.kr";
const char* stationName = "종로구";
const char* returnType = "xml";
const char* numOfRows = "1";
const char* pageNo = "1";
const char* dataTerm = "DAILY";
WiFiClient client;
#define happy epd_bitmap_happy
#define smile epd_bitmap_smile
#define notGood epd_bitmap_notGood
#define bad epd_bitmap_bad
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.display();
delay(2000);
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);
display.setCursor(0, 0);
display.setTextColor(WHITE);
display.drawBitmap(0, 0, epd_bitmap_happy, 32, 32, BLACK, WHITE); // 흰색 아이콘 출력
display.display();
}
void loop() {
String dateNtime, pm10Val, pm10Grade, tmp_str;
static int IntervalReq = 1800;
if (IntervalReq++ > 1800) {
IntervalReq = 0;
Requesthttp();
};
delay(50);
while (client.available()) {
String line = client.readStringUntil('\n');
Serial.println(line);
int i = line.indexOf("</dataTime>");
if (i > 0) {
tmp_str = "<dataTime>";
dateNtime = line.substring(line.indexOf(tmp_str) + tmp_str.length(), i);
}
i = line.indexOf("</pm10Value>");
if (i > 0) {
tmp_str = "<pm10Value>";
pm10Val = line.substring(line.indexOf(tmp_str) + tmp_str.length(), i);
}
i = line.indexOf("</pm10Grade>");
if (i > 0) {
tmp_str = "<pm10Grade>";
pm10Grade = line.substring(line.indexOf(tmp_str) + tmp_str.length(), i);
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");
client.print(String("GET /B552584/ArpltnInforInqireSvc"));
client.print(String("/getMsrstnAcctoRltmMesureDnsty?serviceKey="));
client.print(apiKey);
client.print(String("&returnType=") + returnType);
client.print(String("&numOfRows=") + numOfRows);
client.print(String("&pageNo=") + pageNo);
client.print(String("&stationName=") + urlencode(stationName));
client.print(String("&dataTerm=") + dataTerm);
client.print(version);
client.print(" HTTP/1.1\r\n");
client.print("Host: " + String(server) + "\r\n");
client.print("Connection: close\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, epd_bitmap_happy, 32, 32, BLACK, WHITE);
break;
case 2:
display.drawBitmap(0, 0, epd_bitmap_smile, 32, 32, BLACK, WHITE);
break;
case 3:
display.drawBitmap(0, 0, epd_bitmap_notGood, 32, 32, BLACK, WHITE);
break;
case 4:
display.drawBitmap(0, 0, epd_bitmap_bad, 32, 32, BLACK, WHITE);
break;
}
}
String urlencode(String str) {
String encodedString = "";
char c, code0, 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;
}