13주차 ‐ 공공데이터 활용 - boguuu/SmartDevice_2025-1 GitHub Wiki

📘 API


API (Application Programming Interface) 응용 프로그램 프로그래밍 인터페이스

👉 서로 다른 소프트웨어끼리 정보를 주고받을 수 있게 해주는 통신 규약

✨ 특징


  • 🔗 연결성: 다른 시스템이나 서비스와 쉽게 연결 가능
  • 🔒 보안성: 필요한 정보만 제공하고 나머지는 보호
  • ♻️ 재사용성: 한 번 만든 API를 여러 곳에서 사용 가능
  • 🧩 모듈화: 필요한 기능만 선택해서 사용할 수 있음

💡 예시


  • 🔐 소셜 로그인: 카카오, 네이버, 구글 로그인 API
  • 🌦️ 날씨 정보: 기상청 API, OpenWeather API
  • 🗺️ 지도 기능: 네이버 지도, 카카오맵 API
  • 💳 결제 기능: 토스, 아임포트, 페이코 API
  • 📦 상품 정보: 쿠팡, 위메프 상품 검색 API

EX) 공공 데이터 API


번호 기관 주요 데이터 특징
1️⃣ 기상청 (KMA) 날씨, 기후, 기상 특보 🌦️ 실시간 기상 데이터 제공📆 예보/관측 모두 가능
2️⃣ 환경부 (ME) 대기, 수질, 폐기물 등 환경 정보 🏭 전국 환경 정보 제공🔍 실시간/이력 데이터 조회 가능
3️⃣ 보건복지부 (MOHW) 병원, 약국, 감염병, 복지시설 등 🏥 공공의료 및 복지 데이터 중심📍 위치 기반 검색 지원
4️⃣ 교육부 (MOE) 학교, 학급 수, 교육 통계 🎓 교육 관련 통계 및 기관 정보📊 정기 업데이트 제공
5️⃣ 농림축산식품부 (MAFRA) 농산물 유통, 가격, 재배 정보 🥕 농업/유통 중심 실시간 데이터📈 시장 동향 파악 가능
👉

ESP32를 사용하여 공공데이터 포털에서 미세먼지 정보를 가져와 OLED에 표시 ESP32는 네트워크 사용이 가능하므로 인터넷의 데이터를 가져와 프로젝트에 활용 가능

공공데이터 활용 실습


준비물


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

회로연결


1

1. 공공데이터포털에 접속하여 가입 및 로그인


2

https://www.data.go.kr//

2. 미세먼지 데이터 검색


3

👉

한국환경공단_에어코리아_대기오염정보 Open API 데이터를 활용

3. 활용 신청 정보 입력


4

5

👉

라이센스 표기 동의 후, 활용신청 버튼을 클릭 “측정소별 실시 간 측정정보 조회”를 활용 할것임

4. 인증키 확인 및 설정


6

👉

승인이 완료되면 “일반 인증키”가 발급된 것을 확인

  1. "측정소별 실시간 측정 정보" 조회 클릭 후
  2. serviceKey에 복사해 둔 일반 인증키를 붙여 넣기
  3. numOfRows는 100에서 1로 변경 후
  4. 미리 보기 버튼을 클릭

7

5. 미리보기 확인 및 저장


8

👉

<pm10Value, dataTime, pm10Grade>를 활용할 예정

6. 이미지 준비 및 OLED 아이콘 표시


미세먼지 데이터의 단계는 좋음, 보통, 나쁨, 매우 나쁨 4개의 등급이 있음

이러한 등급을 바탕으로 OLED에 표시

9

6-1. 이미지 준비


OLED LCD size가 128×32이므로 아이콘은 32×32로 준비

10

11

12

13

6-2. 이미지 변환


https://javl.github.io/image2cpp에서C 배열 코드로 변환

14

6-3. images.h 파일 생성


아두이노 IDE에서 새 탭을 생성하고 images.h로 저장

15

6-4. 이미지 배열 작성


변환한 C 배열을 VeryGoodGoodBadVeryBad 네 가지 이름으로 정의 images.h로 파일을 이어준 뒤 코드를 넣어줌

16

17

6-5. 실행코드


#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() {
}

6-6. 실행결과


4img

7. OLED display에 미세먼지 상태 표시


7-1. 실행코드


#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 = "PEO3NsXH8cqTU7k%2Fn0Vwjc4YQcUfqyZQUsFi3L%2FfNQeTuRVuUPiyHZA4xkPVCwSFHxpHmZW%2BMeJ1Nc8aaQYD6g%3D%3D";
const char* version = "&ver=1.0";
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;
}

7-2. 실행결과


Display_fine_dust_status_on_OLED_display

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