13주 차 공공 데이터를 활용한 미세먼지 상태 OLED 디스플레이 출력 - park-02/2024-1_Smart-Devices GitHub Wiki

목차

  • API
  • 공공데이터
  • 실습[1]
  • 실습[2]
  • 후기

API

  • API는 소프트웨어 애플리케이션이 서로 상호작용할 수 있도록 하는 중요한 도구입니다. 데이터를 교환하고, 외부 서비스를 사용하며, 애플리케이션을 통합하는 데 널리 사용됩니다. RESTful API와 SOAP API가 대표적인 예입니다. API를 활용하면 복잡한 기능을 구현하는 데 필요한 시간을 단축하고, 외부 시스템과 쉽게 연동할 수 있습니다.

공공데이터

  • 공공데이터는 정부 및 공공기관이 생성하거나 보유하고 있는 모든 자료와 정보를 가리킵니다. 이러한 데이터는 국민 모두에게 소통과 협력을 촉진하며, 공적인 목적을 위해 활용될 수 있습니다. 각 공공기관은 보유한 데이터 목록을 확인하고, 국민에게 공개할 수 있는 데이터를 온라인 포털에 등록함으로써 공유가 가능하게 됩니다.

프로젝트 미리 보기

  • 이번 실습은 ESP32의 네트워크 기능을 활용하여 인터넷에서 데이터를 가져와 프로젝트에 활용할 수 있습니다. 공공데이터 포털에서 미세먼지 정보를 받아와 OLED 디스플레이에 표시하는 실습입니다.

공공데이터 포털 API 키 신청 절차

124142

미세먼지 데이터 검색

  • 검색창에 "미세먼지"를 입력하고 검색합니다.
  • 원하는 미세먼지 데이터를 제공하는 API를 선택합니다. 이번 실습에서는 “한국환경공단_에어코리아_대기오염 정보”라는 Open API 데이터를 활용해 보겠습니다.

124142

활용신청

  • API 상세 페이지에서 "활용신청" 버튼을 클릭합니다.

124142

활용신청

  • 활용 목적을 입력합니다.

124142

활용신청

  • 라이센스 표기 동의 후, 모든 항목을 작성한 후 "신청" 버튼을 클릭합니다. 이번 실습에서는 “측정소별 실시간 측정 정보 조회”를 활용합니다.

124142

인증키 복사해 두기

  • 승인이 완료되면 '일반 인증키'가 발급됩니다. 이 값은 ESP32에서 사용하므로 복사해 둡니다.

image

데이터 미리 보기

  • 이 챕터에서는 ,, 를 활용할 예정입니다.

124142

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

124142

XML

XML(Extensible Markup Language)은 데이터를 저장하고 전송하기 위한 마크업 언어입니다. 주로 구조화된 데이터를 표현하고, 다른 시스템 간에 정보를 교환하는 데 사용됩니다

파싱(Parsing)

파싱(Parsing)은 컴퓨터 과학에서 주어진 데이터나 문서를 의미 있는 단위로 분해하고 해석하는 과정을 말합니다. 일반적으로 파싱은 특정 형식의 데이터나 문서를 처리하기 위해 사용됩니다.

실습[1]

실습 내용

  • 이번 실습은 미세먼지 데이터의 등급은 "좋음", "보통", "나쁨", "매우 나쁨"의 4가지로 나뉩니다. 이 등급을 이미지로 변환하여 OLED에 표시해 보겠습니다.

이미지 준비하기

  • OLED 디스플레이의 크기가 128×32이므로, 아이콘은 32×32 크기로 준비합니다. 각 아이콘을 별도의 파일로 저장해 둡니다.

image

아래 사이트에서 변환합니다.

124142

images.h 파일 만들기

  • 아두이노 IDE 스케치 에디터 창에서 새 탭을 선택하고, 파일 이름을 "images.h"로 지정합니다.

124142

image

  • 그림 변환 사이트에서 복사해 둔 그림 코드를 복사해 주고 배열 이름을 “VeryGood”으로 변경해 줍니다.

image

회로연결

image

소스코드

10-1 코드

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

동작 원리 및 코드설명

이 코드는 Arduino와 SSD1306 OLED 디스플레이를 초기화하고, 다양한 이미지를 표시하는 예제입니다. setup() 함수에서는 디스플레이를 초기화하고 초기 스플래시 화면을 표시하며, loop() 함수는 비어 있어 디스플레이된 이미지를 유지합니다. OLED 디스플레이의 픽셀 단위 너비와 높이, 그리고 이미지 파일의 포함은 헤더 파일에서 설정됩니다.

images.h

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

실행 및 결과

  • ESP32 보드를 컴퓨터에 연결합니다.
  • Arduino IDE를 열고 해당 소스 코드를 컴파일하고 업로드합니다.
  • "좋음","보통","나쁨","매우 나쁨" 등급 표시되는 이미지가 나타납니다.

KakaoTalk_20240529_145424785

실습[2]

실습 내용

  • 미세먼지 데이터를 받아 와서 등급을 아이콘으로 그려 주고 미세먼지 값을 표시해 보도록 하겠습니다

회로연결

image

소스코드

10_2코드

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <WiFi.h>
#include <time.h>

#include "images.h"  // 이미지 파일을 포함하는 헤더 파일

const char* ssid = "..";          // 사용하는 WiFi 네트워크 이름 (SSID)
const char* password = "123456799";  // 사용하는 WiFi 네트워크 비밀번호
const int httpPort = 80;

const char* apiKey = "Zn2MAqTdKR7z9NxV0sp41ZiF3Zocz2VrooR3L2CSOkH8QJFX9eLyafaGCdCprgNARDc2GPxoQ4mISdYN4w84tA%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;
}

images.h

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

동작 원리 및 코드 설명

이 코드는 ESP32와 SSD1306 OLED 디스플레이를 사용하여 미세먼지 데이터를 수집하고 표시하는 예제입니다. WiFi를 통해 데이터를 가져와 OLED에 날짜, 시간, 미세먼지 농도 등을 표시합니다. IntervalReq 변수를 이용하여 30분마다 데이터를 요청하며, displayString() 함수는 데이터를 OLED에 표시하고, displayIcon() 함수는 미세먼지 등급에 따라 아이콘을 표시합니다. urlencode() 함수는 URL을 인코딩하는데 사용됩니다.

실행 및 결과

  • ESP32 보드를 컴퓨터에 연결합니다.
  • Arduino IDE를 열고 해당 소스 코드를 컴파일하고 업로드합니다.
  • 코드가 실행되면 ESP32는 WiFi를 통해 공공데이터 포털에 요청을 보내 미세먼지 정보를 가져옵니다.
  • 가져온 미세먼지 등급에 따라 OLED에 해당하는 아이콘이 표시됩니다. 예를 들어, "좋음" 등급은 "좋음" 이미지로 나타내고, "매우 나쁨" 등급은 "매우 나쁨" 이미지로 나타냅니다. KakaoTalk_20240529_151014914

후기

  • 이번 실습을 통해 미세먼지 데이터를 받아와서 등급을 아이콘으로 표시하는 방법과 함께 미세먼지 값을 OLED에 표시하는 방법을 배웠습니다. 이를 통해 현장에 센서 데이터를 받아와서 시각적으로 표현하는 과정을 경험할 수 있었습니다.
⚠️ **GitHub.com Fallback** ⚠️