이전 글에서 아두이노와 와이파이 모듈 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에서는 두 번의 연결만에 완료할 수 있습니다.
KOR_Weather_esp32.zip
#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에서 테스트
#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 수신 스케치 파일
#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을 입력하시면 연결 및 수신을 시작합니다.
OPEN_Weather_esp32_5days_Parsing.zip
#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하는 방법 및 코드
'Arduino' 카테고리의 다른 글
아두이노 - 문자열의 이해와 표현 방법 (2) | 2019.11.14 |
---|---|
아두이노 - EEPROM 사용하기 (0) | 2019.11.12 |
ESP8266 / ESP32 - 아두이노 IDE 코어 git 최신버전 설치하기 (0) | 2019.11.08 |
아두이노 - JSON 형식 데이터를 parsing하는 방법 및 코드 (1) | 2019.11.05 |
아두이노 - ESP01 모듈, 기상청 / 오픈웨더맵 API 날씨 정보 받기 (18) | 2019.11.03 |
아두이노 IDE 환경 설정하기 - preferences.txt 설정, 에디터 폰트 변경(추천) (0) | 2019.10.31 |
ESP8266 / ESP32 - time.h 라이브러리 이용 시간 출력 및 NTP 서버 시간 동기화 하기 (0) | 2019.10.28 |
아두이노 - 도트 매트릭스 제어하기, dot matrix (0) | 2019.10.22 |