반응형

이전 글에서 아두이노와 와이파이 모듈 ESP01을 이용해서 인터넷상 날씨 정보를 받아오는 예제를 알아보았다. 아두이노의 경우 성능의 제약으로 인해 기상청 날씨정보를 전문을 스트링 변수에 모두 저장할 수 없었고, 또한 메모리 용량이 아두이노의 작동에 영향을 미칠 정도로 소요되어 다른 라이브러리를 이용한 추가 작업에 어려움이 있을 거라 예상된다.  ESP8266 / ESP32는 기본 메모리와 저장공간이 아두이노 보다 크고 MCU 작동 속도도 빠르다. 무엇보다 메모리와 저장공간이 커져서 복잡한 라이브러리를 여러 개 동작시킬 수도 있고 기상청 날씨정보 RSS 전문을 한 개의 스트링 변수에 저장할 수 있다. ESP8266 / ESP32의 아두이노 코어의 기본 라이브러리인 HTTPClient.h를 이용하여 RSS / API 기반 날씨 정보를 받아보자. 우선 두 모듈에 적용될 날씨정보 수신 코드는 와이파이 라이브러리 명칭만 다를 뿐 나머지는 똑같다.

 

우선 인터넷상 날씨 정보를 받아오기 위해서는 한국 기상청 RSS 주소나 외국 사이트 오픈 웨더 맵의 API key가 필요하게 되는데 RSS 주소/ API key를 얻는 방법 및 주요 설명은 이전 글 아두이노 - ESP01 모듈 wifi 이용, 기상청 RSS / 오픈 웨더 맵 API 날씨 정보 받기를 참조하기 바란다. 

 

2023년 2월 1일 확인해 보니 기상청에서 더 이상 RSS주소 기반 날씨정보를 제공하지 않고 있습니다. 공공데이터포털에서 발급받은 API KEY를 이용하여 날씨정보를 요청하도록 변경되었으므로 그에 맞게 수정해 주었습니다. 공공데이터포털 API KEY 및 기상청 날씨 관련 데이터 항목, 파싱코드 상세 설명등은 상기 링크를 참조하시기 바랍니다. 

 

ESP8266 / ESP32 라이브러리 명칭 비교

// ESP8266 라이브러리 
#include <ESP8266WiFi.h>       // 와이파이 라이브러리 
#include <ESP8266HTTPClient.h> // HTTPClient 라이브러리 

// ESP32 라이브러리 
#include <WiFi.h>              // 와이파이 라이브러리 
#include <HTTPClient.h>        // HTTPClient 라이브러리

 

와이파이 공유기의 아이디와 비밀번호 수정해 주어야 합니다. 

const char* ssid = "skynet";  // 와이파이 공유기 ID
const char* password = "skynet00";   // 와이파이 공유기 비밀번호

 

아래 API KEY를 공공데이터포털에 가입한 뒤 발급받은 API KEY로 수정해 주어야 합니다. 

const char KOR_KEY[] PROGMEM        = "A2igtzhW%2 ...... %3D%3D"; // 공공데이터포털 API KEY

 

아래 스케치는 3시간 단위(0시, 3시, 6시, 9시, 12시, 15시, 18시, 21시)의 날씨 정보를 수신하고 그중 기온, 하늘상태, 습도, 강수량을 파싱 하였습니다. 시리얼 모니터에 1을 클릭하면 기상청 접속 및 날씨 정보 수신이 시작됩니다. 아두이노에서는 23번의 연결을 통해 데이터를 받았었는데 ESP32에서는 두 번의 연결만에 완료할 수 있습니다. 

API 연결 명령 및 기상청 날씨 정보 파싱

 

KOR_Weather_esp32.zip

KOR_Weather_esp32.zip
0.00MB

#include <WiFi.h> 
WiFiClient client;

const char* ssid = "skynet";//"SK_WiFiGIGA40F7";
const char* password = "skynet00";//"1712037218";

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  if (WiFi.status() == WL_CONNECTED) Serial.println("Connected to the WiFi network");
  Serial.println();
}

// 예보 기준 시간: 2, 5, 8, 11, 14, 17, 20, 23 -> 270열 
float lat = 37.49;  // 위도 
float lon = 126.91; // 경도
uint8_t checkTime = 2; 
uint32_t dateTime = 20230203;
uint8_t Rows = 135;  
uint8_t pageNum = 1;
uint8_t totalPage = 2; // 2번에 걸쳐 수신
bool getNext = true; 

// 기온, 하늘상태, 습도, 강우 변수
float  Temp[8] = { 0, }; // 00, 03, 06, 09, 12, 15, 18, 21
uint8_t Sky[8]  = { 0, };
uint8_t Reh[8]  = { 0, }; 
uint8_t Pop[8]  = { 0, };

String convertTime(uint8_t checkTime) {
  String temp;
  if (checkTime < 10) { temp += '0'; temp += checkTime; }
  else temp += checkTime;
  temp += F("00");
  return temp;
}

#define PI 3.1415926535897932384626433832795

int nx, ny;

void getGride(float lat, float lon ) {
  float ra;
  float theta;
  float RE = 6371.00877;   // 지구 반경(km)
  float GRID = 5.0;        // 격자 간격(km)
  float SLAT1 = 30.0;      // 투영 위도1(degree)
  float SLAT2 = 60.0;      // 투영 위도2(degree)
  float OLON = 126.0;      // 기준점 경도(degree)
  float OLAT = 38.0;       // 기준점 위도(degree)
  float XO = 43;           // 기준점 X좌표(GRID)
  float YO = 136;          // 기1준점 Y좌표(GRID)
  float DEGRAD = PI / 180.0;
  float RADDEG = 180.0 / PI;
  float re = RE / GRID;
  float slat1 = SLAT1 * DEGRAD;
  float slat2 = SLAT2 * DEGRAD;
  float olon = OLON  * DEGRAD;
  float olat = OLAT  * DEGRAD;
  float sn = tan( PI * 0.25f + slat2 * 0.5f ) / tan( PI * 0.25f + slat1 * 0.5f );
  sn = log(cos(slat1) / cos(slat2)) / log(sn);
  float sf = tan(PI * 0.25f + slat1 * 0.5f);
  sf = pow(sf, sn) * cos(slat1) / sn;
  float ro = tan(PI * 0.25f + olat * 0.5f);
  ro = re * sf / pow(ro, sn);
  ra = tan(PI * 0.25f + (lat) * DEGRAD * 0.5f);
  ra = re * sf / pow(ra, sn);
  theta = lon * DEGRAD - olon;
  if(theta > PI) theta -= 2.0f * PI;
  if(theta < -PI) theta += 2.0f * PI;
  theta *= sn;
  nx = int(floor(ra * sin(theta) + XO + 0.5f));
  ny = int(floor(ro - ra * cos(theta) + YO + 0.5f));
  Serial.print(F("nx: ")); Serial.println(nx);
  Serial.print(F("ny: ")); Serial.println(ny);
}

//http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst?serviceKey=
//A2igtzhW%2 ...... %3D%3D
//&numOfRows=12&pageNo=1&dataType=JSON&base_date=20230201&base_time=0800&nx=59&ny=125
const char* host = "apis.data.go.kr";
const char KOR_HEAD[] PROGMEM       = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/";
const char KOR_GUSN[] PROGMEM       = "getUltraSrtNcst"; // 초단기실황조회
const char KOR_GUSF[] PROGMEM       = "getUltraSrtFcst"; // 초단기예보조회
const char KOR_GVLF[] PROGMEM       = "getVilageFcst";   // 단기예보조회
const char KOR_ADD0[] PROGMEM       = "?serviceKey=";
const char KOR_KEY[] PROGMEM        = "A2igtzhW%2 ...... %3D%3D"; // 공공데이터포털 API KEY
const char KOR_ADD1[] PROGMEM       = "&numOfRows=";
const char KOR_ADD2[] PROGMEM       = "&pageNo=";
const char KOR_ADD3[] PROGMEM       = "&dataType=JSON&base_date=";
const char KOR_ADD4[] PROGMEM       = "&base_time=";
const char KOR_ADD5[] PROGMEM       = "&nx=";
const char KOR_ADD6[] PROGMEM       = "&ny=";

void get_weather(uint32_t date, String bsTime, uint8_t Rows, uint8_t pageNum) {
  Serial.println(F("Starting connection to server..."));
  delay(100);
  if (client.connect(host, 80)) {
    Serial.println(F("Connected to server"));
    client.print(F("GET "));
    client.print((const __FlashStringHelper *)KOR_HEAD);
    client.print((const __FlashStringHelper *)KOR_GVLF);
    client.print((const __FlashStringHelper *)KOR_ADD0);
    client.print((const __FlashStringHelper *)KOR_KEY);
    client.print((const __FlashStringHelper *)KOR_ADD1);
    client.print(Rows);
    client.print((const __FlashStringHelper *)KOR_ADD2);
    client.print(pageNum);
    client.print((const __FlashStringHelper *)KOR_ADD3);
    client.print(date);
    client.print((const __FlashStringHelper *)KOR_ADD4);
    client.print(bsTime);
    client.print((const __FlashStringHelper *)KOR_ADD5);
    client.print(nx);
    client.print((const __FlashStringHelper *)KOR_ADD6);
    client.print(ny);
    client.print(F(" HTTP/1.1\r\nHost: "));
    client.print(host);
    client.print(F("\r\nConnection: close\r\n\r\n"));
    getNext = false;
  }
}

bool finished = true;
uint8_t count = 0;
bool checkRain = false;

void loop() {
  if (!finished) {
    while (count < totalPage) {
      if (getNext) {
        if (count == 0) getGride(lat, lon); // 위도, 경도
        String cTime = convertTime(checkTime);
        Serial.println(F("--------------------"));
        Serial.print(dateTime); Serial.print('/'); Serial.println(cTime); 
        Serial.print(Rows);Serial.print('/');Serial.println(pageNum);
        get_weather(dateTime, cTime, Rows, pageNum);
      }
      if (client.available()) {
        bool check = false; 
        while (client.available()) { check = client.find("["); if (check) break; }
        Serial.println(F("OK")); 
        if (check) {
          float value = 0; uint8_t timeVal = 255; uint8_t rowC = 0; char cc;
          String line0, cat; 
          while (client.available()) {
            while (rowC < Rows) {
              for (uint8_t i = 0; i < 3; i++) check = client.find(":"); 
              cc = client.read(); cc = 'a'; cat = ""; 
              while (cc != '"') { cc = client.read(); if (cc != '"') cat += cc; }
              for (uint8_t i = 0; i < 2; i++) check = client.find(":"); 
              cc = client.read(); line0 = ""; 
              line0 += char(client.read()); line0 += char(client.read());
              timeVal = line0.toInt(); line0 = "";
              bool save = false;
              if (timeVal == 0 || timeVal == 3 || timeVal == 6 || timeVal == 9 
                          || timeVal == 12 || timeVal == 15 || timeVal == 18 || timeVal == 21) save = true;
              if (save) {
                check = client.find(":"); cc = client.read(); cc = 'a';
                while (cc != '"') { cc = client.read(); if (cc != '"') line0 += cc; }
                value = line0.toFloat(); line0 = "";
              }
              check = client.find("}"); 
              if (save) {
                if (cat == F("TMP")) Temp[timeVal/3] = value;           
                else if (cat == F("SKY")) Sky[timeVal/3] = uint8_t(value); 
                else if (cat == F("REH")) Reh[timeVal/3] = uint8_t(value);
                else if (cat == F("PTY")) { 
                  if (uint8_t(value) == 0) Pop[timeVal/3] = 0;
                  else checkRain = true;
                } else if (checkRain && cat == F("PCP")) {
                  if (uint8_t(value) != 0) { Pop[timeVal/3] = uint8_t(value); checkRain = false; }
                } else if (checkRain && cat == F("SNO")) {
                  if (uint8_t(value) != 0) Pop[timeVal/3] = uint8_t(value); 
                  else Pop[timeVal/3] = 0;  
                  checkRain = false; 
                }
              }
              rowC++; 
            }
            while (client.available()) cc = client.read();
          }
          count++; getNext = true; pageNum++; 
        } else {
          Serial.println(F("No Data, check Date & time")); 
          finished = true; count = 0; pageNum = 1; getNext = true; 
          break;
        }
        if (count == totalPage) { 
          finished = true; checkRain = false; 
          for (int i = 0; i < 8; i++) { Serial.print(Temp[i]); Serial.print(','); }
          Serial.println();
          for (int i = 0; i < 8; i++) { Serial.print(Sky[i]); Serial.print(','); }
          Serial.println();
          for (int i = 0; i < 8; i++) { Serial.print(Reh[i]); Serial.print(','); }
          Serial.println();
          for (int i = 0; i < 8; i++) { Serial.print(Pop[i]); Serial.print(','); }
          Serial.println();
        }
      } 
    }
    count = 0; pageNum = 1; 
  }
  if(Serial.available() > 0){
    String temp = Serial.readStringUntil('\n');
    Serial.println(temp);
    if (temp == "1") {  // 시리얼 모니터에서 날씨정보 요청 명령
      finished = !finished;
    }
  }
}

 

 

 

ESP8266 오픈 웨더 맵 API 수신 스케치 파일 - NodeMcu에서 테스트

8266_httpclient_open_weather.ino
0.00MB

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

const char* ssid = "SK_WiFiGIGA40F7";  // 와이파이 공유기 ID
const char* password = "1712037218";   // 와이파이 공유기 비밀번호

// https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY
const String stpoint = "http://api.openweathermap.org/data/2.5/weather?q=";
const String city = "YOUR_CITY";    // Seoul,KR
const String endpoint = "&APPID=";
const String key = "YOUR_API_KEY";  // 66e47f22f3570e6*****************
String line = "";

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("Connected to the WiFi network");
  get_weather();
}

void get_weather() {
  if ((WiFi.status() == WL_CONNECTED)) { //Check the current connection status
    Serial.println("Starting connection to server...");
    HTTPClient http;
    http.begin(stpoint + city + endpoint + key);       //Specify the URL
    int httpCode = http.GET();  //Make the request
    if (httpCode > 0) {         //Check for the returning code
      line = http.getString();
    }
    else {
      Serial.println("Error on HTTP request");
    }
//    Serial.println(line); // 수신한 날씨 정보 시리얼 모니터 출력
    parsing();
    http.end(); //Free the resources
  }
}

void loop() {
}

void parsing() {
  int del_index = line.indexOf(F("description"));
  line.remove(0, del_index);
  int end_index = line.indexOf(F("icon")); 
  String description;
  description = line.substring(14, end_index - 3);
  Serial.print(F("description: ")); Serial.println(description);
  del_index = line.indexOf(F("temp"));
  line.remove(0, del_index);
  end_index = line.indexOf(F("pressure")); 
  String temp;
  temp = line.substring(6, end_index - 2); 
  Serial.print(F("temp: ")); Serial.println(temp);
  del_index = line.indexOf(F("pressure"));
  line.remove(0, del_index);
  end_index = line.indexOf(F("humidity")); 
  String pressure;
  pressure = line.substring(10, end_index - 2); 
  Serial.print(F("pressure: ")); Serial.println(pressure);
  del_index = line.indexOf(F("humidity"));
  line.remove(0, del_index);
  end_index = line.indexOf(F("temp_min")); 
  String humidity;
  humidity = line.substring(10, end_index - 2); 
  Serial.print(F("humidity: ")); Serial.println(humidity);
  line = "";
}

 

 

ESP32 오픈 웨더 맵 API 수신 스케치 파일

esp32_httpclient_open_weather.ino
0.00MB

#include <WiFi.h> 
#include <HTTPClient.h> 

const char* ssid = "SK_WiFiGIGA40F7";  // 와이파이 공유기 ID
const char* password = "1712037218";   // 와이파이 공유기 비밀번호

// https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY
const String stpoint = "http://api.openweathermap.org/data/2.5/weather?q=";
const String city = "YOUR_CITY";    // Seoul,KR
const String endpoint = "&APPID=";
const String key = "YOUR_API_KEY";  // 66e47f22f3570e6*****************
String line = "";

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("Connected to the WiFi network");
  get_weather();
}

void get_weather() {
  if ((WiFi.status() == WL_CONNECTED)) { //Check the current connection status
    Serial.println("Starting connection to server...");
    HTTPClient http;
    http.begin(stpoint + city + endpoint + key);       //Specify the URL
    int httpCode = http.GET();  //Make the request
    if (httpCode > 0) {         //Check for the returning code
      line = http.getString();
    }
    else {
      Serial.println("Error on HTTP request");
    }
//    Serial.println(line); // 수신한 날씨 정보 시리얼 모니터 출력
    parsing();
    http.end(); //Free the resources
  }
}

void loop() {
}

void parsing() {
  int del_index = line.indexOf(F("description"));
  line.remove(0, del_index);
  int end_index = line.indexOf(F("icon")); 
  String description;
  description = line.substring(14, end_index - 3); 
  Serial.print(F("description: ")); Serial.println(description);
  del_index = line.indexOf(F("temp"));
  line.remove(0, del_index);
  end_index = line.indexOf(F("pressure")); 
  String temp;
  temp = line.substring(6, end_index - 2); 
  Serial.print(F("temp: ")); Serial.println(temp);
  del_index = line.indexOf(F("pressure"));
  line.remove(0, del_index);
  end_index = line.indexOf(F("humidity")); 
  String pressure;
  pressure = line.substring(10, end_index - 2); 
  Serial.print(F("pressure: ")); Serial.println(pressure);
  del_index = line.indexOf(F("humidity"));
  line.remove(0, del_index);
  end_index = line.indexOf(F("temp_min")); 
  String humidity;
  humidity = line.substring(10, end_index - 2); 
  Serial.print(F("humidity: ")); Serial.println(humidity);
  line = "";
}

 

시리얼 모니터에 출력된 날씨 정보

Starting connection to server... 
description: scattered clouds 
temp: 281.55 
pressure: 1022 
humidity: 93

 

오픈웨더맵에서 3시간 단위로 24시간 기상예보를 받는 코드입니다. 시리얼 모니터에 1을 입력하시면 연결 및 수신을 시작합니다. 

 

API 연결 명령 및 오픈웨더맵 날씨 정보 파싱

 

OPEN_Weather_esp32_5days_Parsing.zip

OPEN_Weather_esp32_5days_Parsing.zip
0.00MB

#include <WiFi.h> 
WiFiClient client;

const char* ssid = "skynet";  // 와이파이 공유기 ID
const char* password = "skynet00";   // 와이파이 공유기 비밀번호

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  if (WiFi.status() == WL_CONNECTED) Serial.println("Connected to the WiFi network");
  Serial.println();
}

float lat = 37.49;  // 위도 
float lon = 126.91; // 경도

// http://api.openweathermap.org/data/2.5/forecast?lat={latitude}&lon={longitude}&APPID=YOUR_API_KEY
const char* host = "api.openweathermap.org";
const char OPEN_HEAD[] PROGMEM       = "http://api.openweathermap.org/data/2.5/forecast?lat=";
const char OPEN_ADD0[] PROGMEM       = "&lon=";
const char OPEN_ADD1[] PROGMEM       = "&APPID=";
const char OPEN_KEY[] PROGMEM        = "YOUR_API_KEY";  // YOUR_API_KEY

bool requested = false;

void get_weather() {
  Serial.println(F("Starting connection to server..."));
  if (client.connect(host, 80)) {
    Serial.println(F("Connected to server"));
    client.print(F("GET "));
    client.print((const __FlashStringHelper *)OPEN_HEAD);
    client.print(lat);
    client.print((const __FlashStringHelper *)OPEN_ADD0);
    client.print(lon);
    client.print((const __FlashStringHelper *)OPEN_ADD1);
    client.print((const __FlashStringHelper *)OPEN_KEY);
    client.print(F(" HTTP/1.1\r\nHost: "));
    client.print(host);
    client.print(F("\r\nConnection: close\r\n\r\n"));
    requested = true;
  }
}

bool finished = true;
uint8_t count = 0;
uint8_t stopCount = 0;

void loop() {
  if (!finished) {
    if (!requested) {
      Serial.println(F("--------------------"));
      get_weather();
    }
    while (client.available()) {
      bool check = false;
      while (client.available()) { check = client.find("["); if (check) break; }
      Serial.println(F("OK")); 
      if (check) {
        float tempVal = 0; uint8_t humi = 0; uint8_t rain = 0; uint8_t timeVal = 255; 
        char cc; String line0, main, desc; 
        while (count < 8) {
          for (uint8_t i = 0; i < 3; i++) check = client.find(":"); 
          cc = 'a'; line0 = ""; 
          while (cc != ',') { cc = client.read(); if (cc != ',') line0 += cc; }
          tempVal = line0.toFloat(); 
          for (uint8_t i = 0; i < 7; i++) check = client.find(":"); 
          cc = 'a'; line0 = ""; 
          while (cc != ',') { cc = client.read(); if (cc != ',') line0 += cc; }
          humi = line0.toInt(); 
          for (uint8_t i = 0; i < 4; i++) check = client.find(":"); 
          cc = client.read(); cc = 'a'; main = "";
          while (cc != '"') { cc = client.read(); if (cc != '"') main += cc; }
          check = client.find(":"); 
          cc = client.read(); cc = 'a'; desc = "";
          while (cc != '"') { cc = client.read(); if (cc != '"') desc += cc; }
          for (uint8_t i = 0; i < 9; i++) check = client.find(":"); 
          cc = 'a'; line0 = ""; 
          while (cc != ',') { cc = client.read(); if (cc != ',') line0 += cc; }
          rain = line0.toFloat()*100;
          check = client.find("_"); 
          if (check) {
            for (uint8_t i = 0; i < 17; i++) { cc = client.read(); } 
            while (cc != ':') { cc = client.read(); if (cc != ':') line0 += cc; }
            timeVal = line0.toInt(); 
          }
          check = client.find("}"); 
          if (check) {
            count++;
            Serial.print(timeVal); Serial.print(':'); Serial.print(' ');
            Serial.print(tempVal); Serial.print(',');
            Serial.print(humi); Serial.print(',');
            Serial.print(main); Serial.print(',');
            Serial.print(desc); Serial.print(',');
            Serial.print(rain); Serial.println('%');
          }             
          if (count == 8) { 
            finished = true; requested = false;
            while (client.available()) client.read();
          }
        }
      } else {
        if (!finished && stopCount < 5) { requested = false; stopCount++; }
        else { stopCount = 0; finished = true; requested = false; count = 0; break; }
        delay(100);
      }
    }
    count = 0;
  }
  if(Serial.available() > 0){
    String temp = Serial.readStringUntil('\n');
    Serial.println(temp);
    if (temp == "1") {  // 시리얼 모니터에서 날씨정보 요청 명령
      finished = !finished; 
    }
  }
}

 

 

 

 

 

관련 글

[arduino] - 아두이노 - ESP01 모듈, 기상청 RSS / 오픈웨더맵 API 날씨 정보 받기

[arduino] - 아두이노 - JSON 형식 데이터를 parsing하는 방법 및 코드

 

 

+ Recent posts