', { cookie_domain: 'auto', cookie_flags: 'max-age=0;domain=.tistory.com', cookie_expires: 7 * 24 * 60 * 60 // 7 days, in seconds }); 'Making/ESP32 날씨 표시기' 카테고리의 글 목록 :: MakerLee's Workspace
728x90

Openweathermap에서 날씨를 받아와 사용하려 했는데 데이터 변환 과정에서 골치아픈 부분이 있었습니다. 

차후 4.2" 전자잉크 등 보다 화면이 넓은 기기를 사용하기 위해서 당일의 온도변화, 7일치의 날씨변화 등도 있었으면 했고요.

무엇보다 현재 날씨는 알 수 있는데 당일 날씨나 최저, 최고 온도 등 우리가 흔히 쓰는 기준으로 데이터를 얻기가 힘들더군요.

 

 

그리고 이 모든 것을 ChatGPT4.0 에게 맡겼습니다. 

 

 

 

 

 

 

 

 

저는 그냥 이렇게 해줘, 저렇게 해줘, 여기 틀렸잖아

를 반복하다 보니 코드는 손도 안대고 좋은 결과가 나오더군요

 

 

금일 온도 / 최저 / 최고온도 / 체감온도/ 구름 / 풍속 / 강수량 / 강설량 등이 잘 정리되어 출력된 것을 볼 수 있습니다. 

5일간 날씨 데이터도 마찬가지로 정리되어 나오고 있고요

 

 

 

아래는 이 코드입니다.

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

// WiFi 설정
const char* ssid = "Yourssid";
const char* password = "yourPW";

// OpenWeatherMap API 키
String openWeatherMapApiKey = "YourAPI";

// 서울의 도시명과 국가 코드
String city = "Seoul";
String countryCode = "KR";

// 업데이트 주기 설정 (15초)
unsigned long lastTime = 0;
unsigned long timerDelay = 15000; 

void setup() {
  Serial.begin(115200); // 시리얼 모니터 시작
  Serial.println("serial start"); // 디버깅 메시지 추가
  WiFi.begin(ssid, password); // WiFi 연결 시작
  Serial.println("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected to WiFi");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // 즉시 첫 번째 API 호출을 위해 lastTime 초기화
  lastTime = millis() - timerDelay;
}

void loop() {
  // 타이머가 만료되었을 때 데이터 요청
  if ((millis() - lastTime) > timerDelay) {
    if (WiFi.status() == WL_CONNECTED) {
      // API 요청 URL 구성 - 3시간 간격의 5일 예보 데이터
      String serverPath = "https://api.openweathermap.org/data/2.5/forecast?q=" + city + "," + countryCode + "&appid=" + openWeatherMapApiKey + "&units=metric";
      
      // 데이터 요청 및 수신
      String weatherData = httpGETRequest(serverPath.c_str());
      if (weatherData != "{}") { 
        parseWeatherData(weatherData); // 수신된 데이터 파싱
      }
      lastTime = millis();
    } else {
      Serial.println("WiFi Disconnected");
    }
  }
}

// HTTP GET 요청 함수
String httpGETRequest(const char* serverName) {
  WiFiClientSecure client; 
  client.setInsecure();    // SSL 인증서 검증 무시
  HTTPClient http;
  http.begin(client, serverName); // HTTPS 연결 시작
  Serial.println("Requesting URL: " + String(serverName));

  int httpResponseCode = http.GET();
  
  String payload = "{}";
  if (httpResponseCode == 200) {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  } else {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
    payload = http.getString(); 
    Serial.println("Error response: " + payload);
  }
  http.end();
  return payload;
}

// 타임스탬프를 한국 표준시(KST)로 변환하는 함수
String convertUnixTimeToKST(long unixTime) {
  unixTime += 9 * 3600; // UTC+9 시간대 보정
  time_t t = unixTime;
  struct tm *tmStruct = localtime(&t);
  char buffer[30];
  strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tmStruct);
  return String(buffer);
}

// 날씨 데이터 파싱 함수
void parseWeatherData(String json) {
  JSONVar myObject = JSON.parse(json);
  if (JSON.typeof(myObject) == "undefined") {
    Serial.println("Parsing failed!");
    return;
  }

  // 오늘 날씨 데이터 파싱 및 출력
  Serial.println("Today's Weather Data:");
  JSONVar list = myObject["list"];

  // 하루 동안의 데이터에서 최저/최고 온도 계산
  float dayMinTemp = 1000;
  float dayMaxTemp = -1000;
  for (int i = 0; i < 8; i++) { // 24시간 내의 3시간 간격 데이터
    JSONVar data = list[i];
    float temp_min = (float)(double)data["main"]["temp_min"];
    float temp_max = (float)(double)data["main"]["temp_max"];
    if (temp_min < dayMinTemp) dayMinTemp = temp_min;
    if (temp_max > dayMaxTemp) dayMaxTemp = temp_max;
  }
  JSONVar todayData = list[0];
  float temp = (float)(double)todayData["main"]["temp"];
  float feels_like = (float)(double)todayData["main"]["feels_like"];
  const char* weather_description = (const char*)(todayData["weather"][0]["description"]);
  float humidity = (float)(double)todayData["main"]["humidity"];
  float clouds = (float)(double)todayData["clouds"]["all"];
  float wind_speed = (float)(double)todayData["wind"]["speed"];
  float rain = todayData.hasOwnProperty("rain") ? (float)(double)todayData["rain"]["3h"] : 0;
  float snow = todayData.hasOwnProperty("snow") ? (float)(double)todayData["snow"]["3h"] : 0;

  Serial.print("Temp: ");
  Serial.println(temp);
  Serial.print("Min Temp: ");
  Serial.println(dayMinTemp);
  Serial.print("Max Temp: ");
  Serial.println(dayMaxTemp);
  Serial.print("Feels Like: ");
  Serial.println(feels_like);
  Serial.print("Humidity: ");
  Serial.println(humidity);
  Serial.print("Clouds: ");
  Serial.println(clouds);
  Serial.print("Weather: ");
  Serial.println(weather_description);
  Serial.print("Wind Speed: ");
  Serial.println(wind_speed);
  Serial.print("Rain Volume (3h): ");
  Serial.println(rain);
  Serial.print("Snow Volume (3h): ");
  Serial.println(snow);
  Serial.println("-------");

  // 5일간 날씨 데이터 파싱 및 출력
  Serial.println("5 Days Weather Data:");
  for (int day = 0; day < 5; day++) {
    float minTemp = 1000;
    float maxTemp = -1000;
    float avgHumidity = 0;
    float avgClouds = 0;
    float avgWindSpeed = 0;
    float totalRain = 0;
    float totalSnow = 0;
    int count = 0;
    String weatherDescriptions = "";

    for (int i = day * 8; i < (day + 1) * 8 && i < list.length(); i++) {
      JSONVar dayData = list[i];
      float temp_min = (float)(double)dayData["main"]["temp_min"];
      float temp_max = (float)(double)dayData["main"]["temp_max"];
      float humidity = (float)(double)dayData["main"]["humidity"];
      float clouds = (float)(double)dayData["clouds"]["all"];
      float wind_speed = (float)(double)dayData["wind"]["speed"];
      float rain = dayData.hasOwnProperty("rain") ? (float)(double)dayData["rain"]["3h"] : 0;
      float snow = dayData.hasOwnProperty("snow") ? (float)(double)dayData["snow"]["3h"] : 0;
      const char* weather_description = (const char*)(dayData["weather"][0]["description"]);

      if (temp_min < minTemp) minTemp = temp_min;
      if (temp_max > maxTemp) maxTemp = temp_max;
      avgHumidity += humidity;
      avgClouds += clouds;
      avgWindSpeed += wind_speed;
      totalRain += rain;
      totalSnow += snow;
      weatherDescriptions = String(weather_description);
      count++;
    }
    avgHumidity /= count;
    avgClouds /= count;
    avgWindSpeed /= count;

    long date = (long)list[day * 8]["dt"];
    Serial.print("Date: ");
    Serial.print(convertUnixTimeToKST(date));
    Serial.print(", Min Temp: ");
    Serial.print(minTemp);
    Serial.print(", Max Temp: ");
    Serial.print(maxTemp);
    Serial.print(", Humidity: ");
    Serial.print(avgHumidity);
    Serial.print(", Clouds: ");
    Serial.print(avgClouds);
    Serial.print(", Wind Speed: ");
    Serial.print(avgWindSpeed);
    Serial.print(", Rain Volume (3h): ");
    Serial.print(totalRain);
    Serial.print(", Snow Volume (3h): ");
    Serial.print(totalSnow);
    Serial.print(", Weather: ");
    Serial.println(weatherDescriptions);
    Serial.println("-------");
  }
}

 

 

그렇지만 이 모든 걸 갈아엎게 되는데.. 다음 포스팅에 이어서 쓰겠습니다.

728x90
728x90

검색해 보면 날씨정보를 받아오는 방법은 여러 가지입니다.

기상청이나 여타 날씨관련 사이트 등에서도 가능하지만 여러 모로 알아본 결과 openweathermap 이 편리할 것 같더군요




1. 오픈웨더맵 가입 - api 를 생성합니다.

 

 

이후 

http://api.openweathermap.org/data/2.5/weather?q=yourCityName,yourCountryCode&APPID=yourUniqueAPIkey

위 텍스트에서 yourCityName / yourCountryCode / yourUniqueAPIkey를 수정해야 하는데요.

 

 

 

 

서울을 예로 들면

검색하면 이렇게 나오는데 yourCityName 은 Seoul, yourCountryCode 은 KR 을 넣으면 됩니다

그리고 마지막 yourUniqueAPIkey 를 처음 발급받은 API 를 복사해서 수정하면 전체 주소가 완성됩니다.

 

 

 

 

 

 

그 주소를 인터넷 창에 입력해보면 다음과 같이 날씨 데이터가 표시되는 것을 알 수 있습니다

 

 

 

이제 이 API 주소를 이용해 ESP32C3 에서 날씨 데이터를 받아와 보겠습니다.

맨 먼저 날씨 데이터가 JSON 형식으로 나오기 때문에 이를 처리할 Arduino_JSON 라이브러리를 설치해 줬습니다.

 

 

 

 

 

 

이번에는 초기 코드를 99% 이상 ChapGPT 4.0에게 맡겼습니다. 

예전보다 훨씬 뛰어나져서 원하는 대로 코드를 깔끔하게 짜 주네요.

저는 세부 사항 몇가지를 조정하고 두세번 다시 물어보는 정도로 코드를 완성할 수 있었습니다. 

 

 

 

전체 코드는 아래와 같습니다.

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

const char* ssid = "YourWifiID";
const char* password = "YourWifiPassword";

// Your Domain name with URL path or IP address with path
String openWeatherMapApiKey = "YourAPI";

// Replace with your country code and city
String city = "Seoul";
String countryCode = "KR";

// Timer set to 15 seconds for demonstration
unsigned long lastTime = 0;
unsigned long timerDelay = 15000;

String jsonBuffer;

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.println("Timer set to 15 seconds (timerDelay variable), it will take 15 seconds before publishing the first reading.");
}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    if (WiFi.status() == WL_CONNECTED) {
      String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&APPID=" + openWeatherMapApiKey;
      jsonBuffer = httpGETRequest(serverPath.c_str());
      JSONVar myObject = JSON.parse(jsonBuffer);

      if (JSON.typeof(myObject) == "undefined") {
        Serial.println("Parsing input failed!");
        return;
      }

      Serial.print("Latitude: ");
      Serial.println(myObject["coord"]["lat"]);
      Serial.print("Longitude: ");
      Serial.println(myObject["coord"]["lon"]);
      
      Serial.println("Weather Details:");
      for (int i = 0; i < myObject["weather"].length(); i++) {
        Serial.print("  Weather ID: ");
        Serial.println(myObject["weather"][i]["id"]);
        Serial.print("  Main: ");
        Serial.println(myObject["weather"][i]["main"]);
        Serial.print("  Description: ");
        Serial.println(myObject["weather"][i]["description"]);
        Serial.print("  Icon: ");
        Serial.println(myObject["weather"][i]["icon"]);
      }

      Serial.print("Temperature: ");
      Serial.println(myObject["main"]["temp"]);
      Serial.print("Feels Like: ");
      Serial.println(myObject["main"]["feels_like"]);
      Serial.print("Minimum Temperature: ");
      Serial.println(myObject["main"]["temp_min"]);
      Serial.print("Maximum Temperature: ");
      Serial.println(myObject["main"]["temp_max"]);
      Serial.print("Pressure: ");
      Serial.println(myObject["main"]["pressure"]);
      Serial.print("Humidity: ");
      Serial.println(myObject["main"]["humidity"]);

      Serial.print("Wind Speed: ");
      Serial.println(myObject["wind"]["speed"]);
      Serial.print("Wind Direction (Degrees): ");
      Serial.println(myObject["wind"]["deg"]);
      if (myObject["wind"].hasOwnProperty("gust")) {
        Serial.print("Wind Gust: ");
        Serial.println(myObject["wind"]["gust"]);
      }
      
      if (myObject.hasOwnProperty("rain")) {
        Serial.print("Rain Volume for last hour: ");
        Serial.println(myObject["rain"]["1h"]);
      }
      
      if (myObject.hasOwnProperty("snow")) {
        Serial.print("Snow Volume for last hour: ");
        Serial.println(myObject["snow"]["1h"]);
      }

      Serial.print("Visibility: ");
      Serial.println(myObject["visibility"]);
      
      Serial.print("Cloudiness (%): ");
      Serial.println(myObject["clouds"]["all"]);

      lastTime = millis();
    } else {
      Serial.println("WiFi Disconnected");
    }
  }
}

String httpGETRequest(const char* serverName) {
  WiFiClient client;
  HTTPClient http;
  http.begin(client, serverName);
  int httpResponseCode = http.GET();
  String payload = "{}"; 

  if (httpResponseCode > 0) {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  } else {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  http.end();
  return payload;
}

 

 

 

 

 

아래가 출력 결과입니다.

완벽합니다.

 

 

 

 

 

여담이지만 재미있던 것은 제가 출력 반복 간격을 늦추려고 10000(10초)를 15000(15초)로 변경했더니 

바로 다음 질답에서 시리얼 출력의 'Timer set to 10 seconds...' 부분을 'Timer set to 15 seconds...' 로 바꾸더군요

이런 센스(?)까지 발휘하는 모습에 다소 놀랐습니다. 

 

 

728x90
728x90

 

 

타오바오에서 너무 많이 사는 바람에 혼자서는 평생 쓰고도 남을 만큼의 전자잉크가 생겨서 사용처를 고민중이었습니다. 

그러다가 마음에 드는 프로젝트를 발견했죠.

 

 

 

 

 

 

 

전기도 많이 먹지 않고, 리프레쉬도 자주 필요 없는 이런 날씨정보 디스플레이 용으로 좋겠더군요.

 

 

 

 

 

 

원래는 전용 드라이버 보드를 제작해서 컨트롤러를 대체하려고 했지만 실패하고..

디버깅 중에 어차피 비용도 비슷하게 들 것 같아 그냥 편한 쪽으로 일단 진행하기로 했습니다. 

 

 

 

 

 

 

 

 

컨트롤러는 ESP32C3 보드를 사용할 예정입니다.

 

 

 

 

 

가격은 무려 2천원대에 블루투스, 와이파이도 되면서 ESP32의 성능을 가져 성능도 부족할 게 없고요.

보다시피 엄청 아담한 사이즈라 사용하기에도 좋습니다. 

728x90

+ Recent posts