스마트디바이스실습 ‐ 13주차 - jiho0419/SmartDevice_2025-1 GitHub Wiki

1️⃣ API

*️⃣ 정의

💠 API(Application Programming Interface) 는 소프트웨어 구성 요소 간의 상호 작용을 정의하는 인터페이스을 말한다.
💠 응용 프로그램들이 서로 통신하고 데이터를 주고받기 위해 사용하는 명세(specification)로, 현대 소프트웨어 개발에서 핵심적인 역할을 말한다.

*️⃣ 주요 역할

🔸 기능 재사용
🔸 모듈 간 통신
🔸 플랫폼 간 연동
🔸 개발 효율성 향상

*️⃣ 구성 요소

💠 엔드포인트(Endpoint)

🔸API가 제공하는 서비스의 주소. 보통 URL 형식으로 구성됨

💠 요청 메서드(Request Method)

🔸 클라이언트가 서버에 요청을 보낼 때 사용하는 방식
🔸 GET: 데이터 조회
🔸 POST: 데이터 생성
🔸 PUT: 데이터 전체 수정
🔸 PATCH: 데이터 일부 수정
🔸 DELETE: 데이터 삭제

💠 헤더(Header)

🔸 인증 정보, 콘텐츠 타입 등 요청/응답에 대한 메타데이터가 담김

💠 본문(Body)

🔸 주로 POST, PUT 요청 시 데이터를 담는 부분(JSON, XML 등으로 전달)

💠 응답(Response)

🔸 서버가 클라이언트의 요청에 대해 반환하는 결과

*️⃣ 종류

분류 기준 종류 및 설명
접근 방식 오픈 API : 누구나 사용 가능
접근 방식 프라이빗 API : 내부 시스템 전용
통신 방식 REST API : HTTP 기반, 가장 널리 사용됨
통신 방식 SOAP API : XML 기반 메시지 교환
통신 방식 GraphQL API : 원하는 데이터만 조회 가능
데이터 형식 JSON, XML, YAML 등
용도 기준 웹 API, 운영체제 API(예: Windows API), 라이브러리 API

*️⃣ 장 / 단점

💠 장점

🔸 시스템 간 통합 용이
🔸 개발 시간 단축
🔸 기능 재사용
🔸 확장성 우수

💠 단점

🔸 인증 및 보안 이슈
🔸 API 변경 시 클라이언트 영향 발생
🔸 관리 복잡성 증가

*️⃣ 실제 활용 사례

🔸 카카오 API : 로그인, 지도, 메시지 전송 등 제공
🔸 네이버 API : 검색, 쇼핑, 번역 등
🔸 OpenAI API : 챗봇, 언어 생성, 요약 기능 등 제공
🔸 페이팔/토스 API : 결제 기능 연동

2️⃣ 실습

*️⃣ OLED 디스플레이 아이콘 표시

💠 ESP32를 이용하여 아이콘 이미지를 OLED 디스플레이에 표시하는 실습 진행

💠 준비물

🔸 ESP32
🔸 ESP32 확장 쉴드
🔸 OLED 디스플레이


💠 회로 연결

ESP32 실드 OLED 디스플레이
S : D21 SDA
S : D22 SCL
VCC VCC
GND GND

💠 코드

// main 코드 
#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() {
}
// images.h 코드 
// 'happy', 32x32px
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
};
// 'smile', 32x32px
const unsigned char Good [] 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 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, 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 VeryBad [] 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] = {
	VeryGood,
	Good,
	Bad,
	VeryBad
};

💠 실행 방법

🔸 아이콘 이미지 설정
🔸 OLED LCD size가 128×32 > 아이콘 이미지 size 32×32 준비


🔸 이미지 변환 설정
🔸 https://javl.github.io/image2cpp 접속 > 위 아이콘 이미지 파일 선택


🔸 size >32 X 32 Background color > Black 선택
🔸 Dithering > Binery Brightness > 110 선택
🔸 Code output format > Arduino code > epd_bitmap_ > Generate code 선택
🔸 해당 아이콘 이미지 변환 코드를 복사


🔸 images.h 파일 생성
🔸 새 탭 > 파일 이름을 images.h 로 설정 > 위에 변환한 이미지 코드를 입력
🔸 main에 위 코드를 입력하여 업로드 진행


💠 실습 결과

🔸 OLED 디스플레이에 변환한 아이콘 이미지들이 표시됨

*️⃣ 공공테이터 활용하여 미세먼지 정보 표시

💠 공공데이터 포털의 미세먼지 정보를 활용하여 해당 정보를 OLED 디스플레이에 표시하는 실습 진행

💠 코드

#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 = "인증키(URL Encode)";
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;
}

💠 실행 방법

🔸 공공데이터 포털
🔸 www.data.go.kr 접속 > 회원가입 및 로그인


🔸 미세먼지 데이터
🔸 한국환경공단_에어코리아_대기오염정보 검색


🔸 활용목적 > 웹 사이트 개발 > ESP32 개발 입력
🔸 이용허락범위 > 동의합니다 > 활용신청 클릭


🔸 설정이 끝난 후 나온 일반 인증키 복사 > 실시간 정보 조회할 시 인증키 필요


🔸 실시간 미세먼지 정보를 조회를 위해 > 측정소별 실시간 측정정보 조회 클릭
🔸 serviceKey > 위에 복사한 기본 인증키 입력
🔸 numOfRows > 1 입력 > 미리보기 클릭


🔸 미리보기를 통해 미세먼지 정보를 알 수 있음
🔸 위 코드를 업로드하여 OLED에 해당 정보와 같은 지 확인


💠 실습 결과

🔸 미리보기에 확인했던 정보와 같이 뜨는 것을 확인
🔸 아이콘도 미세먼지 정보에 맞게 변경 된 것을 확인

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