13주차 ‐ 공공데이터 활용 - movie-01/SmartDevice GitHub Wiki

13주차 - 공공데이터 활용


개요

  • API
  • 실습 1
  • 실습 2

API

API(응용 프로그램 프로그래밍 인터페이스, Application Programming Interface)는 서로 다른 소프트웨어 간의 상호작용을 가능하게 해주는 인터페이스다. 쉽게 말해, API는 프로그램이 다른 프로그램의 기능을 사용할 수 있게 만들어주는 약속된 규칙이자 통신 창구다.


✅ 1. API의 정의

  • 기능: 한 시스템이 다른 시스템의 기능을 직접 접근하지 않고, 정해진 형식으로 요청을 보내면 그에 맞는 응답을 돌려주는 구조.
  • 형식: REST API, GraphQL, gRPC 등 다양한 방식이 존재.
  • 역할 예시:
    • 날씨 정보를 보여주는 앱 → 기상청 API 호출
    • 로그인 시 Google/Facebook 연동 → OAuth API 사용

✅ 2. API의 활용 예시

분야 활용 사례
웹 개발 외부 결제 시스템 연동 (카카오페이, 토스 등)
앱 개발 Google Maps API를 활용한 위치 기반 서비스
데이터 분석 공공데이터 포털 API를 이용한 실시간 통계 수집
클라우드 AWS, Azure API로 서버 관리 자동화
금융 증권 API로 주식 정보/거래 자동화 (예: 키움 Open API)

✅ 3. API의 장점

장점 설명
재사용성 기존 시스템의 기능을 다시 만들 필요 없이 API만 호출하면 됨
유연성 다양한 클라이언트(모바일, 웹 등)에서 동일한 API 활용 가능
표준화 일정한 호출 방식과 응답 형식으로 개발 일관성 유지
보안성 직접 접근 없이 인증된 접근만 허용 가능 (OAuth, API Key 등)

✅ 4. API의 단점

단점 설명
의존성 외부 API가 변경되거나 중단되면 시스템 전체에 영향
속도 제한 호출 횟수 제한 (Rate Limit)으로 인해 대량 요청 어려움
보안 취약점 인증 정보 노출 시 보안 위협 가능성 존재
디버깅 어려움 응답 지연, 오류 원인을 외부 시스템에서 알기 어려움

✅ 5. 최근 사용 사례 예시 (2024~2025)

🎯 공공 분야

  • 한국 공공데이터포털 API:
  • 전국 대기질, 미세먼지, 인구 통계 등 실시간 데이터 수집
  • 공공데이터포털: https://www.data.go.kr

🎯 ChatGPT API

  • OpenAI GPT API:
  • 기업들이 고객상담 챗봇, 이메일 요약, 회의록 자동화 등에 도입
  • → 예: 삼성, 네이버, LG CNS 등 국내 대기업들 사용 확대

🎯 금융

  • 증권사 API (키움, 미래에셋):
  • 개인 투자자들이 자동매매 봇 개발, 백테스팅 등에 활용
  • → 키움증권 Open API+, 한국투자증권 REST API

🎯 SNS 연동

  • Instagram, TikTok API:
  • 쇼핑몰에서 상품 연동, 해시태그 기반 리뷰 크롤링 자동화 등에 사용

실습 1

  • 준비물
    • ESP32 보드
    • ESP32 확장 실드
    • OLED 디스플레이 (128x32)

image


  1. "https://javl.github.io/image2cpp/" 사이트에 접속하고, 실습에 필요한 32x32 사진을 준비한다.

image


  1. 파일을 선택하고, 아래와 같이 준비하고 "Generate code" 실행한다

image

  1. 아두이노에서 "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() {
}

결과

1


실습 2

  1. 공공데이터 포털에 접속 후 회원 가입 및 로그인을 한다
  2. 검색 창에 “한국환경공단_에어코리아_대기오염정보” 입력 후 아래 사진과 같이 Open API 데이터를 찾아 "활용 신청" 클릭

image


  1. 활용 목적을 입력하고 라이센스 표기 동의 후 -> 측정소별 실시 간 측정정보 조회를 활용
  2. 승인이 완료 후 -> 일반 인증키 발급
  3. 아래 사진 참고하여 코드 작성

image


코드 작성

#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;
}

결과

2

⚠️ **GitHub.com Fallback** ⚠️