반응형

아두이노에서 날씨 정보를 받기 위해서는 기상 서비스 사이트의 API KEY가 있어야 합니다. 한국의 경우 공공데이터포털, 외국 사이트는 OpenWeatherMap에서 회원가입한 뒤 KEY를 할당받아 코딩해 주어야 합니다. 

 

1. 한국 기상청의 날씨 정보 받기

예전에는 기상청에서 RSS기반으로 XML형식의 기상 정보를 제공했었으나 현재는 OpenWeatherMap과 같이 API기반으로 변경되었습니다.

 

RSS 기반 한국 기상청

RSS(Really Simple Syndication, Rich Site Summary)란 블로그처럼 콘텐츠 업데이트가 자주 일어나는 웹사이트에서, 업데이트된 정보를 쉽게 구독자들에게 제공하기 위해 XML을 기초로 만들어진 데이터 형식입니다. RSS 서비스를 이용하면 업데이트된 정보를 찾기 위해 홈페이지에 일일이 방문하지 않아도 업데이트될 때마다 빠르고 편리하게 확인할 수 있습니다.

 

변경전 : RSS - 기상청 날씨누리 홈페이지 -> 현재의 아두이노 코드로는 수신 실패
XML 형식의 데이터만 제공
예보 시간 기준 4시간 이후의 날씨부터 3시간 단위의 날씨 정보 제공
회원가입 없이 동네단위의 RSS주소만으로 데이터 수신

변경후 : API - 공공데이터 포털
XML / JSON형식의 데이터 제공
예보 시간 기준 1시간 이후의 날씨부터 1시간 단위의 날씨 정보 제공
회원가입한 뒤 API사용을 신청하고 API KEY를 할당받은 뒤 그 KEY를 이용하여 테이터 수신
전송 데이터의 페이지 기능 추가

 

공공데이터포털 API KEY생성

기상청의 날씨 정보를 제공하는 API를 활용하기 위해서는 공공데이터포털에 회원가입해야 로그인 후 API 사용 신청을 해야 합니다. 

 

https://www.data.go.kr/

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

 

검색창에서 "기상청 단기예보 조회서비스"를 입력하고 검색합니다. 

 

오픈 API항목에서 "기상청_단기예보 ((구)_동네예보) 조회서비스"를 찾고 활용신청을 합니다. 

활용목적에 사용하고자 하는 목적에 따라 항목을 선택하시면 되는데 아두이노에서 사용하는 경우에는 기타를 선택하고 "임베디드 시스템(아두이노 등)에서 날씨정보 활용 개발 / 자료공유"와 같이 목적을 서술해 줍니다. 

 

 

첨부파일은 공란으로 해도 되고 라이선스 표시 부분의 "동의합니다."를 체크하고 활용신청을 하면 자동승인에 따라 아래와 같이 표시됩니다. 

활용승인이 된 뒤에는 인증키 발급현황에서 날씨 정보를 수신하는 데 사용하는 API KEY를 확인할 수 있습니다. 

 

 

API key를 이용하여 기상청 날씨 정보를 수신하는 방법은 "기상청41_단기예보 조회서비스_오픈API활용가이드"에 설명되어 있습니다.

 

2023년 2월 기준 "기상청41_단기예보 조회서비스_오픈API활용가이드.zip"

기상청41_단기예보 조회서비스_오픈API활용가이드_최종.zip
1.78MB

 

날씨 예보 데이터를 요청하기 위해서는

아래의 형식으로 공공데이터 포털에 데이터 전송을 요청해야 합니다. 

 

http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/{ 요청 서비스 }?serviceKey={ 인증키 }&numOfRows={ 행 개수 }&pageNo={ 페이지 번호 }({ 데이터 타입 })&base_date={ 날짜 }&base_time={ 예보 시간 }&nx={ 위도 격자값 }&ny={ 경도 격자값 }

 

1. 단기예보 조회서비스를 활용하기 위한 서비스 URL: 

http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0

 

2. 요청 서비스

단기예보 조회서비스에는 4개의 이용 가능한 서비스가 있습니다.  

단기예보 조회서비스 getUltraSrtNcst 초단기실황조회
getUltraSrtFcst 초단기예보조회
getVilageFcst 단기예보조회
getFcstVersion 예보버전조회

 

해당 서비스에 따르는 Call Back URL: 

http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst

 

3. 인증키 

공공데이터포털 사이트에서 발급받은 인증키를 드래그하여 복사한 뒤 붙여 넣기로 사용하시면 됩니다. 

대괄호를 포함한 "{ 인증키 }"를 인증키(예. "A2igtzhW%2By . . . . %3D%3D)로 대체해 줍니다.  

테스트용 API KEY : 아래 KEY는 코드 테스트용으로만 사용하시기 바랍니다. 

"A2igtzhW%2ByXkcL7kFKRlaT81NdvwkihPLD1iz0%2BJcNeR2anoHCimnX8xH%2BMbgeRGBOKwfzGS8ChZ88rfKG5%2B6w%3D%3D"

 

4. 행 개수

날씨 예보에 사용되는 코드값을 기준으로 코드값에 해당되는 정보들 모두를 한 개의 행으로 취급합니다.  

* 코드값 TMP: 1시간 기온

 

XML 형식

<baseDate>20230201</baseDate>
<baseTime>0200</baseTime>
<category>TMP</category>
<fcstDate>20230201</fcstDate>
<fcstTime>0300</fcstTime>
<fcstValue>2</fcstValue>
<nx>59</nx>
<ny>125</ny>

 

JSON 형식

{"baseDate":"20230129","baseTime":"0200","category":"TMP","fcstDate":"20230129","fcstTime":"0300","fcstValue":"-6","nx":59,"ny":125}

 

5. 페이지 번호

전체 예보 코드의 행 개수를 4번에서 지정된 행 개수로 나뉜 페이지 중 전송받고자 하는 페이지 번호입니다. 

예를 들어 전체 예보 코드가 30개의 행으로 구성되어 있을 때 전송받고자 하는 행 개수를 5로 설정하면 6개의 페이지가 데이터 포털 사이트에 생성됩니다. 이중 전송받고자 하는 페이지 번호를 설정하고 해당 페이지만을 전송받을 수 있으며 아두이노처럼 메모리의 크기가 작은 경우에 데이터를 분할해서 받는데 활용합니다. 

 

6. 데이터 타입

날씨 예보 데이터의 기본 타입은 XML입니다. 만약 JSON 형식으로 수신하고자 한다면 ({ 데이터 타입 })을 "&dataType=JSON"로 대체해 주어야 합니다. 

기본 타입인 XML로 받고자 한다면 () 부분을 삭제해 줍니다. 

 

XML 형식

http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/{ 요청 서비스 }?serviceKey={ 인증키 }&numOfRows={ 행 개수 }&pageNo={ 페이지 번호 }&base_date={ 날짜 }&base_time={ 예보 시간 }&nx={ 위도 격자값 }&ny={ 경도 격자값 } 

 

JSON 형식

http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/{ 요청 서비스 }?serviceKey={ 인증키 }&numOfRows={ 행 개수 }&pageNo={ 페이지 번호 }&dataType=JSON&base_date={ 날짜 }&base_time={ 예보 시간 }&nx={ 위도 격자값 }&ny={ 경도 격자값 }

 

7. 날짜

20230201 형식의 8개 문자로 이루어진 문자열로 이루어진 날짜 정보를 입력합니다.

* 테스트 시 당일 또는 어제 날짜로 테스트하시기 바랍니다. 더 오래된 날짜는 아래와 같이 데이터가 없다고 표시됩니다. 

{"response":{"header":{"resultCode":"10","resultMsg":"최근 3일 간의 자료만 제공합니다."}}}

{"response":{"header":{"resultCode":"03","resultMsg":"NO_DATA"}}}

 

Open API 에러 코드 정리

에러코드 에러메세지 설명
00 NORMAL_SERVICE 정상
01 APPLICATION_ERROR 어플리케이션 에러
02 DB_ERROR 데이터베이스 에러
03 NODATA_ERROR 데이터없음 에러
04 HTTP_ERROR HTTP 에러
05 SERVICETIME_OUT 서비스 연결실패 에러
10 INVALID_REQUEST_PARAMETER_ERROR 잘못된 요청 파라메터 에러
11 NO_MANDATORY_REQUEST_PARAMETERS_ERROR 필수요청 파라메터가 없음
12 NO_OPENAPI_SERVICE_ERROR 해당 오픈API서비스가 없거나 폐기됨
20 SERVICE_ACCESS_DENIED_ERROR 서비스 접근거부
21 TEMPORARILY_DISABLE_THE_SERVICEKEY_ERROR 일시적으로 사용할 수 없는 서비스 키
22 LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR 서비스 요청제한횟수 초과에러
30 SERVICE_KEY_IS_NOT_REGISTERED_ERROR 등록되지 않은 서비스키
31 DEADLINE_HAS_EXPIRED_ERROR 기한만료된 서비스키
32 UNREGISTERED_IP_ERROR 등록되지 않은 IP
33 UNSIGNED_CALL_ERROR 서명되지 않은 호출
99 UNKNOWN_ERROR 기타에러

 

 

8. 예보 시간

수신하고자 하는 예보 시간을 0200 형식의 4개 문자로 이루어진 문자열로 입력합니다. 

 

9. 위도 / 경도 격자값

기상청은 지도상 위치의 위도 및 경도값을 기준으로 한 고유한 격자값을 생성하고 그 격자값을 기준으로 날씨 정보를 전송한다고 합니다. 따라서 수신하고자 하는 동네의 위도 / 경도값을 확인한 다음 격자값으로 변환하여 요청하여야 합니다. 예전 RSS기반으로 날씨정보를 전송할 때에는 동네코드를 입력하면 서버에서 격자값으로 변경하여 정보를 전송해 준 것으로 생각됩니다. 현재에는 격자값을 입력하는 방법으로만 데이터를 받을 수 있습니다. 오픈 API 활용가이드에 게시된 "단기예보 지점 좌표(X, Y) 위치와 위경도 간의 전환 C 프로그램 예제"와 아래 사이트의 PHP로 작성된 변환 코드를 참조하여 아두이노에서 사용할 수 있도록 위도 / 경도값을 격자값으로 변환하는 코드를 작성해 주었습니다. 

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

 

참조 사이트

https://gist.github.com/fronteer-kr/14d7f779d52a21ac2f16

 

조회서비스별 코드값 항목 및 예보 기준 시간

조회 서비스에 따라 제공하는 코드값(날씨항목, 예. 기온, 습도, 강우, 바람세기 등)의 개수가 다르며 이 개수(행 개수)가 예보 시간에 해당하는 날씨정보가 됩니다.

 

1. 초단기실황조회 

8개의 코드값으로 8행이 예보시간의 날씨정보입니다.

 

코드값 또는 항목값

예보구분 항목값 항목명 단위
초단기실황 T1H 기온
RN1 1시간 강수량 mm
UUU 동서바람성분 m/s
VVV 남북바람성분 m/s
REH 습도 %
PTY 강수형태 코드값
VEC 풍향 deg
WSD 풍속 m/s

◼ +900이상, –900 이하 값은 Missing 값으로 처리
관측장비가 없는 해양 지역이거나 관측장비의 결측 등으로 자료가 없음을 의미

 

예보 기준 시간

기준 시간 생성시간 Base_time API 제공 시간(~이후) 기준 시간 생성시간 Base_time API 제공 시간(~이후)
00 00:30 0000 00:40 12 12:30 1200 12:40
01 01:30 0100 01:40 13 13:30 1300 13:40
02 02:30 0200 02:40 14 14:30 1400 14:40
03 03:30 0300 03:40 15 15:30 1500 15:40
04 04:30 0400 04:40 16 16:30 1600 16:40
05 05:30 0500 05:40 17 17:30 1700 17:40
06 06:30 0600 06:40 18 18:30 1800 18:40
07 07:30 0700 07:40 19 19:30 1900 19:40
08 08:30 0800 08:40 20 20:30 2000 20:40
09 09:30 0900 09:40 21 21:30 2100 21:40
10 10:30 1000 10:40 22 22:30 2200 22:40
11 11:30 1100 11:40 23 23:30 2300 23:40

 

2. 초단기예보조회  
10개의 코드값으로 LGT 6개의 값부터 순서대로 PTY, RN1, SKY, T1H, REH, UUU, VVV, VEC, WSD 표시되며 60행이 전체 예보시간의 날씨정보입니다.

코드값 또는 항목값

예보구분 항목값 항목명 단위
초단기예보 T1H 기온
RN1 1시간 강수량 범주 (1 mm)
SKY 하늘상태 코드값
UUU 동서바람성분 m/s
VVV 남북바람성분 m/s
REH 습도 %
PTY 강수형태 코드값
LGT 낙뢰 kA(킬로암페어)
VEC 풍향 deg
WSD 풍속 m/s

◼ +900이상, –900 이하 값은 Missing 값으로 처리
관측장비가 없는 해양 지역이거나 관측장비의 결측 등으로 자료가 없음을 의미

예보 기준 시간 및 예보 시간

※ 매시간 30분에 생성되고 10분마다 최신 정보로 업데이트(기온, 습도, 바람)

기준
시간
생성
시각
Base
time
API
제공 시간
(~이후)
예보시간 (매 발표시각마다 6시간 예보)
h시~h+1시 h+1시~h+2시 h+2시~h+3시 h+3시~h+4시 h+4시~h+5시 h+5시~h+6시
00 00:30 0030 00:45 0~1 1~2 2~3 3~4 4~5 5~6
01 01:30 0130 01:45 1~2 2~3 3~4 4~5 5~6 6~7
02 02:30 0230 02:45 2~3 3~4 4~5 5~6 6~7 7~8
03 03:30 0330 03:45 3~4 4~5 5~6 6~7 7~8 8~9
04 04:30 0430 04:45 4~5시 5~6 6~7 7~8 8~9 9~10
05 05:30 0530 05:45 5~6 6~7 7~8 8~9 9~10 10~11
06 06:30 0630 06:45 6~7 7~8 8~9 9~10 10~11 11~12
07 07:30 0730 07:45 7~8 8~9 9~10시 10~11 11~12 12~13
08 08:30 0830 08:45 8~9 9~10 10~11 11~12 12~13 13~14
09 09:30 0930 09:45 9~10 10~11 11~12 12~13 13~14 14~15
10 10:30 1030 10:45 10~11 11~12 12~13 13~14 14~15 15~16
11 11:30 1130 11:45 11~12 12~13 13~14 14~15 15~16시~ 16~17
12 12:30 1230 12:45 12~13 13~14 14~15 15~16 16~17 17~18
13 13:30 1330 13:45 13~14 14~15 15~16 16~17 17~18 18~19
14 14:30 1430 14:45 14~15 15~16 16~17 17~18 18~19 19~20
15 15:30 1530 15:45 15~16 16~17 17~18 18~19 19~20 20~21
16 16:30 1630 16:45 16~17 17~18 18~19 19~20 20~21 21~22
17 17:30 1730 17:45 17~18 18~19 19~20 20~21 21~22 22~23
18 18:30 1830 18:45 18~19 19~20 20~21 21~22 22~23 23~24
19 19:30 19030 19:45 19~20 20~21 21~22 22~23 23~24 24~1
20 20:30 2030 20:45 20~21 21~22 22~23 23~24 22~23 1~2
21 21:30 2130 21:45 21~22 22~23 23~24 0~1 1~2 2~3
22 22:30 2230 22:45 22~23 23~24 0~1 1~2 2~3 3~4
23 23:30 2330 23:45 23~24 0~1 1~2 2~3 3~4 4~5

 

3. 단기예보조회
최대 13개, 최소 12개의 코드값으로 13행 또는 12행이 예보시간의 날씨정보입니다. 최대 행이 되는 시간은 TMN(일 최저기온)이 제공되는 0600시와 TMX(일 최고기온)이 제공되는 1500시입니다. 

* 예보 기준시간을 기준으로 24시간 동안의 날씨 예보 정보를 받고자 한다면 290행을 받아야 합니다.


코드값 또는 항목값

예보구분 항목값 항목명 단위
단기예보 POP 강수확률 %
PTY 강수형태 코드값
PCP 1시간 강수량 범주 (1 mm)
REH 습도 %
SNO 1시간 신적설 범주(1 cm)
SKY 하늘상태 코드값
TMP 1시간 기온
TMN 일 최저기온
TMX 일 최고기온
UUU 풍속(동서성분) m/s
VVV 풍속(남북성분) m/s
WAV 파고 M
VEC 풍향 deg
WSD 풍속 m/s

◼ +900이상, –900 이하 값은 Missing 값으로 처리
관측장비가 없는 해양 지역이거나 관측장비의 결측 등으로 자료가 없음을 의미

예보 기준 시간 및 예보 시간

예보 기준 시간을 기준으로 1시간마다의 날씨예보를 제공합니다.

- Base_time : 0200, 0500, 0800, 1100, 1400, 1700, 2000, 2300 (1일 8회)
- API 제공 시간(~이후) : 02:10, 05:10, 08:10, 11:10, 14:10, 17:10, 20:10, 23:10

 

특정 요소의 코드값 및 범주

- 하늘상태(SKY) 코드 : 맑음(1), 구름 많음(3), 흐림(4)

하늘상태 전운량
맑음 0  5
구름많음 6  8
흐림 9  10

 

- 강수형태(PTY) 코드 : (초단기) 없음(0), 비(1), 비/눈(2), 눈(3), 빗방울(5), 빗방울눈날림(6), 눈날림(7)
(단기) 없음(0), 비(1), 비/눈(2), 눈(3), 소나기(4)

 

- 초단기예보, 단기예보 강수량(RN1, PCP) 범주 및 표시방법(값)

범주 문자열표시
0.1 ~ 1.0mm 미만 1.0mm 미만
1.0mm 이상 30.0mm 미만 실수값+mm
(1.0mm~29.9mm)
30.0 mm 이상 50.0 mm 미만 30.0~50.0mm
50.0 mm 이상 50.0mm 이상

-, null, 0 값은 ‘강수 없음

예) PCP = 6.2 일 경우 강수량은 6.2mm, PCP = 30 일 경우 강수량은 30.0~50.0mm

주) 강수량이 0 ~ 0.1mm 미만일 경우 강수없음으로 표시되나 PTY 값은 0이 아닌 다른값으로 표시됨.

 

- 신적설(SNO) 범주 및 표시방법(값)

범주 문자열표시
0.1 ~ 1.0cm 미만 1.0cm 미만
1.0cm 이상 5.0cm 미만 실수값+cm
(1.0cm~4.9cm)
5.0 cm 이상 5.0cm 이상

-, null, 0 값은 ‘적설 없음’

 

기상청 날씨누리 홈페이지에서 GPS 위치정보 위도 / 경도 확인하는 방법

https://www.weather.go.kr/w/index.do

 

기상청 날씨누리

기상청 날씨누리

www.weather.go.kr

 

아래 그림처럼 중요지명 입력에 신대방동을 선택하여 날씨 정보를 확인해 봅니다. 이때 지도 상단 우측에 지도 새창열림 아이콘을 클릭합니다. 

 

새창으로 열린 지도의 주소창을 보면 경도(126.91), 위도(37.49)를 확인할 수 있습니다. 

 

아두이노에서 날씨 정보 수신하기

아두이노 우노에서 와이파이를 통해 기상청 날씨 정보를 수신하기 위해서는 ESP01과 같은 와이파이 모듈을 사용해야 합니다. 이 와이파이 모듈은 시리얼 통신을 통해 아두이노 우노와 데이터를 주고받게 됩니다. 

 

아두이노와 Wifi 모듈 ESP01 연결

ESP01의 시리얼 통신 보드 레이트가 9600으로 설정되어 있어야 합니다. ESP01의 연결 설정 및 자세한 방법에 관해서는 이전 글 아두이노 - ESP01 wifi 모듈 무선 원격제어 그리고 시리얼 통신- 6편을 참조하기 바랍니다.   

 

만약 시리얼 통신 TTL 레벨 쉬프트(5V -> 3.3V 변환)를 적용하고자 한다면 아래 연결도를 참조하시기 바랍니다. 

 

참조 사이트

https://arduino.stackexchange.com/questions/44336/esp-01-adapter-and-logic-level-converter

 

아두이노에서 HTTPclient를 이용하기 위해 WiFiEsp 라이브러리를 사용하겠습니다.

아래 사이트에서 WiFiEsp 라이브러리를 다운로드하시기 바랍니다.

https://github.com/bportaluri/WiFiEsp

 

다운로드 후 압축을 풀면 아래와 같이 WiFiEsp-master 폴더 안에 똑같은 WiFiEsp-master 폴더가 있는데 안의 폴더명에서 "-master"삭제하여 WiFiEsp로 변경한 뒤 폴더를 복사합니다.

내 컴퓨터의 아두이노 저장 폴더 -> 라이브러리 폴더로 이동하여 붙여 넣기를 해줍니다.

C:\Program Files (x86)\Arduino\libraries

아두이노 IDE가 실행되고 있다면 아두이노 IDE를 반드시 종료하고 다시 실행해 주어야만 라이브러리가 반영됩니다.

아래 그림처럼 파일 -> 예제 ->  WiFiEsp -> WebClient 예제를 실행시깁니다.                        

WiFiEsp 라이브러리 메시지

ESP01과 아두이노 우노 사이의 연결이 잘되어있고 시리얼통신 코드가 적절하게 코딩되어 있다면 아래와 같은 메시지가 출력됩니다. 

[WiFiEsp] Initializing ESP module
[WiFiEsp] Warning: Unsupported firmware 3.0.5
[WiFiEsp] Connected to 공유기 ID

 

시리얼 통신 핀의 연결이 잘못된 경우 

[WiFiEsp] Initializing ESP module
[WiFiEsp] >>> TIMEOUT >>>
[WiFiEsp] >>> TIMEOUT >>>

[WiFiEsp] >>> TIMEOUT >>>
[WiFiEsp] Cannot initialize ESP module

 

시리얼 통신 핀의 연결은 잘되어 있는데 상기와 같은 메시지를 출력하면서 동시에 ESP01의 데이터 전송 시 표시되는 파란색 LED가 점멸하고 있다면 펌웨어를 지우고 다시 업로드하는 공장 초기화 과정이 필요합니다. 펌웨어 업로드 방법은 아래 포스트를 참조하시기 바랍니다. 

[Arduino] - 아두이노 - 와이파이, ESP01 wifi 모듈 무선 원격제어 그리고 시리얼 통신 - 6편

 

시리얼통신 코드가 적절하게 코딩되지 않은 경우

[WiFiEsp] Initializing ESP module

[WiFiEsp] >>> TIMEOUT >>>
[WiFiEsp] Warning: Unsupported firmware 3.0.5
[WiFiEsp] Connected to 공유기 ID

연결은 되지만 TIMEOUT 메시지가 출력됩니다. 아두이노에서 소프트웨어 시리얼을 시작하게 되면 완료되는데 시간이 소요됩니다. 소프트웨어 시리얼 초기화가 완료되지 않은 상태에서 ESP01 모듈을 초기화하기 위해 AT COMMAND를 보내게 되면 초기화 명령이 ESP01에 전송되지 않게 되고 일정시간 후 WiFiEsp 라이브러리가 응답 실패로 TIMEOUT 메시지를 출력하는 것으로 생각됩니다. 따라서 소프트웨어 시리얼과 ESP01 초기화 코드 사이에 딜레이를 주어야 합니다. 라이브러리를 사용하므로 WiFiEsp 예제 스케치에서는 AT COMMAND 명령 코드가 보이지 않습니다. 아마도 라이브러리 소스코드에 있을 것으로 생각합니다.

  Serial.begin(9600);  //시리얼모니터
  esp01.begin(9600);   //와이파이 시리얼
  delay(1000);
  WiFi.init(&esp01);   // initialize ESP

 

아래 스케치는 상기 예제를 현재 아두이노와 ESP01 모듈의 연결 상태에 맞게 소프트 시리얼 코드들을 수정해 주고 날씨정보 요청 메시지를 3번에 걸쳐 전송하도록 수정해 준 코드입니다.

* 요청 메시지 : 단기예보조회 - 12행 - 1페이지 ~ 3페이지 

 

KOR_Weather_WiFiEsp_Basic.zip

KOR_Weather_ATcommand_Basic.zip
0.00MB

 

스케치는 프로그램 저장 공간 14986 바이트(46%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 585바이트(28%)를 사용, 1463바이트의 지역변수가 남음.  최대는 2048 바이트. 

 

코드 설명

시리얼 통신 설정

#include <SoftwareSerial.h> 
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

 

연결하고자 하는 와이파이 공유기 ID 및 비밀번호 설정

char ssid[21] = "skynet";    // your network SSID (name)
char pass[21] = "skynet00";  // your network password

 

아두이노 우노의 메모리를 확보하기 위해 날씨정보 수신을 위한 URL 및 요청 메시지를 PROGMEM으로 코딩

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%2ByXkcL7kFKRlaT81NdvwkihPLD1iz0%2BJcNeR2anoHCimnX8xH%2BMbgeRGBOKwfzGS8ChZ88rfKG5%2B6w%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=";

 

날씨정보 요청 변수

// 예보 기준 시간: 2, 5, 8, 11, 14, 17, 20, 23 
float lat = 37.49;  // 위도 
float lon = 126.91; // 경도
uint8_t checkTime = 2; // 예보 기준 시간
uint32_t dateTime = 20230131; // 날짜
uint8_t Rows = 12;    // 한페이지당 행개수
uint8_t pageNum = 1;  // 받고자 하는 페이지 번호
uint8_t totalPage = 23; // 전체 페이지 개수
bool getNext = true;  // 페이지 컨트롤 변수

 

PROGMEM 메크로 코드를 사용한 날씨정보 요청 함수

void get_weather(uint32_t date, String bsTime, uint8_t Rows, uint8_t pageNum) {
  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 *)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;
  } else {
    failCount++; delay(500); 
    Serial.println(failCount);
    if (failCount > 5) {
      Serial.println(F("Failed & Finished the task")); 
      failCount = 0; finished = true; count = 0; pageNum = 1; getNext = true; 
    }
  }
}

 

* 요청 메시지 전문(참조용)

기상청에서 예전에 RSS 방식으로 날씨 정보를 제공할 때의 수정전 HTTP 요청 형식에 대한 설명입니다.

현재는 "/wid/queryDFSRSS.jsp?zone=1159068000"를 새로운 주소형식으로 변경해 준 것과 같습니다. 

client.print("GET /wid/queryDFSRSS.jsp?zone=1159068000 HTTP/1.1\r\nHost: www.kma.go.kr\r\nConnection: close\r\n\r\n");
 client.print("GET /wid/queryDFSRSS.jsp?zone=1159068000 HTTP/1.1\r\n " 
                         "Host: www.kma.go.kr\r\n"                                
                         "Connection: close\r\n\r\n");
두 번째 코드를 살펴보면 좀 더 명확하게 이해할 수 있다.
"GET /wid/queryDFSRSS.jsp?zone=1159068000 HTTP/1.1\r\n" 코드가 RSS를 요청하는 코드이다.
"Host: www.kma.go.kr\r\n"와 "Connection: close\r\n\r\n" 코드는 요청을 위한 TCP 연결을 종료하는 코드이다. 즉 기상청 서버에 "날씨 정보 RSS를 요청했고 더 이상 요청할 게 없다."정도로 해석하면 되겠다. TCP 연결이 종료되어도 client 연결이 종료되지는 않는 것 같다. 만약 client 연결이 종료된다면 데이터 수신을 할 수 없게 된다.
참고로 "Connection: Keep-Alive" 옵션은 TCP 연결을 계속 유지한다는 것이다.
여기서 "\r\n"는 "캐리지 리턴, 라인피드"이다. 둘 모두 줄 바꿈 역할을 하고 서버의 코드에 따라 "\n"을 사용하거나 "\r" 또는 "\r\n"를 사용할지를 결정해야 한다. 서버의 코드에 따라 어떤 것을 사용해도 정상 작동할 수도 있고, 특정 줄 바꿈을 사용해만 하는 경우도 있게 된다. 아두이노 시리얼 모니터의 전송 옵션인 "새 줄", "캐리지 리턴", "Both NL & CR"의 선택 기준은 아두이노에 업로드된 코드이다. 어떤 옵션을 수신의 종료로 인식하도록 코딩을 하였는가에 따라 정해지게 된다. 만약 상기 코드를 시리얼 모니터에서 보낸다고 가정한다면 전송 옵션을 "Both NL & CR"으로 설정한 상태에서 보내야 하는 것이다.

한 줄로 된 코드에 사이트 주소 값이 포함된 변수를 이용하여 작성하면 아래와 같다.
client.print("GET " + url + " HTTP/1.1\r\n" +  
                 "Host: " + host + "\r\n" +  
                 "Connection: close\r\n\r\n");

 

loop() 함수 내의 날씨 정보 요청 및 데이터 수신 시 처리 코드

if (!finished) { // 데이터 수신이 완료되지 않은경우
    while (count < totalPage) { // 받고자하는 페이지만큼 받지 않은 동안에
      if (getNext) {    // 다음 페이지가 필요하다면
        if (count == 0) getGride(37.49, 126.91); // 위도, 경도 // 처음 요청시만 작동
        String cTime = convertTime(checkTime);   // uint8_t 자료형의 예보 기준 시간을 문자열로 변환
        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()) { // apis.data.go.kr에 연결이 되어 있다면
        char cc;                // 데이터 수신 변수
        while (client.available()) { // apis.data.go.kr에 연결이 되어 있는 동안
          cc = client.read();        // ESP01 데이터 버퍼에서 1바이트씩 읽기
          Serial.print(cc);          // 시리얼 모니터에 출력
          if (cc == '}') Serial.println(); // 1바이트 값이 문자'}'라면 줄바꿈
        } // 모든 데이터 수신후 연결이 종료되면 
        while (client.available()) cc = client.read(); // 버퍼에 데이터가 남아있다면
        count++; getNext = true; pageNum++; failCount = 0; // 다음 연결을 위해 변수 설정
        if (count == totalPage) { 
          finished = true; 
        }
      } 
      if (finished) break; // 연결 실패 카운터가 만족된 경우 
    } // 받고자 하는 페이지를 모두 받았으면
    count = 0; pageNum = 1; // 날씨정보 수신 종료 및 변수 초기화
  }

 

시리얼 모니터에서 날씨정보를 수신하도록 하는 플래그 finished를 변경하여 명령하는 코드 

if(Serial.available() > 0){
  String temp = Serial.readStringUntil('\n');
  Serial.println(temp);
  if (temp == "1") {  // 시리얼 모니터에서 날씨정보 요청 명령
    finished = !finished;
  }
}

 

시리얼 모니터에 출력된 날씨 정보는 아래와 같으며 totalPage만큼 반복합니다. 

--------------------
20230202/0200
12/1
Starting connection to server...
[WiFiEsp] Connecting to apis.data.go.kr
Connected to server
HTTP/1.1 200 OK
Content-Language: ko-KR
Set-Cookie: JSESSIONID=UM7WZkQQuxjH1h4nr4yNtLA9FngDKoFL0OC7LihznN8COK3ybS64YUk1O4Z1FX42.amV1c19kb21haW4vbmV3c2t5Mw==; Path=/1360000/VilageFcstInfoService_2.0; HttpOnly; Domain=apis.data.go.kr
Access-Control-Allow-Origin: *
Content-Type: application/json;charset=UTF-8
Content-Length: 1778
Date: Thu, 02 Feb 2023 10:15:56 GMT
Connection: close
Server: NIA API Server

{"response":{"header":{"resultCode":"00","resultMsg":"NORMAL_SERVICE"}
,"body":{"dataType":"JSON","items":{"item":[{"baseDate":"20230202","baseTime":"0200","category":"TMP","fcstDate":"20230202","fcstTime":"0300","fcstValue":"-3","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"UUU","fcstDate":"20230202","fcstTime":"0300","fcstValue":"1.3","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"VVV","fcstDate":"20230202","fcstTime":"0300","fcstValue":"-2.5","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"VEC","fcstDate":"20230202","fcstTime":"0300","fcstValue":"333","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"WSD","fcstDate":"20230202","fcstTime":"0300","fcstValue":"2.8","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"SKY","fcstDate":"20230202","fcstTime":"0300","fcstValue":"1","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"PTY","fcstDate":"20230202","fcstTime":"0300","fcstValue":"0","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"POP","fcstDate":"20230202","fcstTime":"0300","fcstValue":"0","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"WAV","fcstDate":"20230202","fcstTime":"0300","fcstValue":"0","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"PCP","fcstDate":"20230202","fcstTime":"0300","fcstValue":"강수없음","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"REH","fcstDate":"20230202","fcstTime":"0300","fcstValue":"65","nx":59,"ny":125}
,{"baseDate":"20230202","baseTime":"0200","category":"SNO","fcstDate":"20230202","fcstTime":"0300","fcstValue":"적설없음","nx":59,"ny":125}
]}
,"pageNo":1,"numOfRows":12,"totalCount":846}
}
}

 

날씨 데이터 파싱하기

3시간 단위(0시, 3시, 6시, 9시, 12시, 15시, 18시, 21시)의 날씨 정보중 기온, 하늘상태, 습도, 강수량을 파싱해 보겠습니다. 

파싱 한 값을 저장하기 위해 변수를 배열로 설정해 주었습니다. 

// 기온, 하늘상태, 습도, 강우 변수
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, };

 

기상청에서 전송한 실질적인 날씨 데이터는 문자 '['에서 시작하여 문자 ']'에서 종료됩니다. 문자 '[' 있으면 데이터를 수신한 것으로 확인되어 파싱을 진행합니다. 

while (client.available()) { check = client.find("["); if (check) break; }

 

파싱 코드

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 = "";  // 문자 '"' 제거, cc 변수 초기화, 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 변수 초기화
      line0 += char(client.read()); line0 += char(client.read()); // 예보 시간 문자 두개 저장
      timeVal = line0.toInt(); line0 = ""; // 변수 timeVal에 정수값 저장, 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'; // 값 앞으로 이동, 문자 '"'제거, cc 변수 초기화
        while (cc != '"') { cc = client.read(); if (cc != '"') line0 += cc; } // 날씨 값 저장
        value = line0.toFloat(); line0 = ""; // 변수 value에 소수로 변환 저장, line0 변수 초기화
      }
      check = client.find("}"); // 행 마지막 이동
      if (save) {
        if (cat == F("TMP")) Temp[timeVal/3] = value;   // 카테고리가 TMP이면 소수값 저장
        else if (cat == F("SKY")) Sky[timeVal/3] = uint8_t(value); // 카테고리가 SKY이면 정수값 저장
        else if (cat == F("REH")) Reh[timeVal/3] = uint8_t(value); // 카테고리가 REH이면 정수값 저장
        else if (cat == F("PTY")) { // 카테고리가 PTY이면
          if (uint8_t(value) == 0) Pop[timeVal/3] = 0; // 그 값이 0이면 눈/비 없음
          else checkRain = true;                       // 그 값이 0이 아니면 눈 또는 비 -> 추가 확인
        } else if (checkRain && cat == F("PCP") {      // 추가확인 이고 카테고리가 PCP이면
          if (uint8_t(value) != 0) { Pop[timeVal/3] = uint8_t(value); checkRain = false; } // 그 값이 0이아니면 값 저장, 확인 종료
        } else if (checkRain && cat == F("SNO")) {     // 추가확인 이고 카테고리가 SNO이면
          if (uint8_t(value) != 0) Pop[timeVal/3] = uint8_t(value); // 그 값이 0이아니면 값 저장, 확인 종료
          else Pop[timeVal/3] = 0;  
          checkRain = false; 
        }
      }
      rowC++; // 다음 행 이동
    } // 전체 행만큼 실행
    while (client.available()) cc = client.read(); // 전체행 실행 종료후 나머지 데이터 버리기
  } // CLIENT 연결이 종료 되면
  count++; getNext = true; pageNum++; failCount = 0; // 다음 페이지 연결을 실행하기위한 플래그 또는 값 설정
} else { // 문자 '[' 없으면
  Serial.println(F("No Data, check Date & time")); // 요청 날짜 및 시간 확인, 3일이전 날짜는 NO DATA
  getNext = true; failCount++;
  Serial.println(failCount);
  if (failCount > 3) {
    while (client.available()) char c = client.read();
    Serial.println(F("Failed & Finished the task")); 
    failCount = 0; finished = true; count = 0; pageNum = 1; getNext = true; // 초기화
    break; // while (count < totalPage) 루프 나가기
  }
}
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();
}

 

날씨정보 중 눈과 비와 관련된 날씨코드는 POP, PTY, PCP, SNO 4개로 정의되어 있습니다. 이중 한 개의 값만을 표시하고자 한다면 1시간 강수량인 PCP나 1시간 신적설인 SNO를 표시해야 한다고 생각합니다. PTY는 눈이나 비가 내리는 상태를 표시하고 POP는 강수확률로써 둘 모두 어느 정도의 양이 내리는지 알 수 없습니다. 강수확률이 80% 일지라도 강수량이 1mm 미만으로 흩날리는 비가 잠깐 온다면 실질적으로 비가 왔다고 할 수 없을 것입니다. 따라서 PTY가 없음(0)이 아니라면 다음으로 강수량인 PCP를 확인하고 만약 강수량이 0이라면 눈의 양(SNO)을 확인해야 합니다. 

 

* Serial.find("문자열") 함수: 시리얼 버퍼에서 "문자열"을 찾아 문자열이 있으면 true를 반환하며 "문자열"을 포함한 이전 데이터는 삭제하고 "문자열" 이후부터 데이터를 수신합니다.

 

KOR_Weather_WiFiEsp_Parsing.zip

KOR_Weather_WiFiEsp_Parsing.zip
0.00MB

 

스케치는 프로그램 저장 공간 18604 바이트(57%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 662바이트(32%)를 사용, 1386바이트의 지역변수가 남음.  최대는 2048 바이트.

 

시리얼 모니터에 출력된 23회에 걸쳐 날씨정보를 수신하고 파싱 한 데이터입니다.

--------------------
20230202/0200
12/22
Starting connection to server...
[WiFiEsp] Connecting to apis.data.go.kr
Connected to server
OK
--------------------
20230202/0200
12/23
Starting connection to server...
[WiFiEsp] Connecting to apis.data.go.kr
Connected to server
OK
-3.00,-3.00,-4.00,-4.00,-1.00,2.00,0.00,-2.00,
3,1,1,1,3,3,3,3,
50,65,65,60,40,35,35,45,
0,0,0,0,0,0,0,0,

 

* WiFiEsp 라이브러리를 사용하지 않고 AT COMMAND를 사용한 스케치입니다. 메모리를 절약할 수 있습니다.

KOR_Weather_ATcommand_Basic.zip

KOR_Weather_ATcommand_Basic.zip
0.00MB

스케치는 프로그램 저장 공간 11622 바이트(36%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 428바이트(20%)를 사용, 1620바이트의 지역변수가 남음. 최대는 2048 바이트.

 

KOR_Weather_ATcommand_Parsing.zip

KOR_Weather_ATcommand_Parsing.zip
0.00MB

스케치는 프로그램 저장 공간 15138 바이트(46%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 499바이트(24%)를 사용, 1549바이트의 지역변수가 남음. 최대는 2048 바이트.

 

2. OpenWeatherMap에서 날씨 정보 받기

 

API 기반 OpenWeatherMap

OpenWeatherMap은 웹 서비스 및 모바일 응용 프로그램 개발자에게 현재 날씨 데이터, 예측 및 기록 데이터를 포함한 날씨 데이터를 제공하는 온라인 서비스입니다.

 

OpenWeatherMap은 사이트에 가입을 한 뒤에 API KEY를 생성하고 아두이노에 적용해야 기상 정보를 받을 수 있게 되는데 가입을 해야 한다는 불편한 점이 있기는 하지만 현재 날씨를 제공한다는 차이점이 있고 데이터의 길이가 매우 짧기에 아두이노에 적용하기가 좀 더 편리하긴 하다. 다만, 날씨 정보에서 현재 기온의 정보가 기상청의 기온과 차이가 많이 나는 경우가 종종 있다. 

 

오픈 웨더 맵

https://openweathermap.org/

가입하기 위해서는 Sign Up을 클릭하면 되고, 로그인 후에 API KEY를 설정하는 페이지가 안 보일 경우에는 Sign in을 클릭하면 쉽게 찾을 수 있다.

API KEY를 생성하기 위해서는 로그인 후 아래 그림의 API keys 탭을 클릭하면 key 생성 페이지로 이동한다.

 

Default 키가 미리 생성되어 있고 추가로 Name 창에 이름을 입력하고 Generate를 클릭하면 아래처럼 새로운 API key가 생성된다.

API key를 이용하여 날씨 정보를 수신하기 위한 방법에 관한 설명은 홈페이지의 API를 클릭하면 아래 그림처럼 현재 날씨와 시간별 예보, 날짜별 예보에 관한 "API doc" 버튼을 클릭하면 각 예보 별로 API key를 사용하는 방법을 확인할 수 있다.

현재 기상정보의 설명을 보면 아래와 같은 정보들을 "api.openweathermap.org"에 전송해 주어야 한다. 

 

api.openweathermap.org/data/2.5/weather?q={city name},{country code}

{city name}은 날씨 정보를 얻고자 하는 도시 이름이다. 

{country code}는 한국의 경우 KR이다. 

 

생성한 API key를 아두이노 코드에 적용하는 방법은 Guide를 클릭하고 아래 그림 항목에 설명되어 있다.

https://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=YOUR_API_KEY

국가명 뒤에 이어서 "&appid=YOUR_API_KEY"를 붙여주면 된다고 한다. 

 

서울의 현재 날씨 정보를 받고자 한다면 아래와 같다.

https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY 

 

아두이노 코드에 적용하면 아래와 같다.

(상기 그림의 sample API key를 사용했다 - 실제 적용하려면 생성한 API key로 변경해 주어야 한다.)

const char* host = "api.openweathermap.org"; 
const String url = "/data/2.5/weather?q=Seoul,KR&APPID=b6907d289e10d714a6e88b30761fae22";

 

도시명을 찾고자 한다면 Maps -> Weather maps를 클릭하면 표시되는 지도에서 확인할 수 있다. 지도를 크게 확대하면 더 많은 도시명을 찾을 수 있다.

WebClient.ino
0.00MB

#include "WiFiEsp.h"

#include <SoftwareSerial.h> 
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

char ssid[] = "SK_WiFiGIGA40F7";            // your network SSID (name)
char pass[] = "1712037218";        // your network password
int status = WL_IDLE_STATUS;     // the Wifi radio's status

// https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY
const char* host = "api.openweathermap.org";
const String url = "/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY";

// Initialize the Ethernet client object
WiFiEspClient client;

void setup() {
  Serial.begin(9600);  //시리얼모니터
  esp01.begin(9600);   //와이파이 시리얼
  WiFi.init(&esp01);   // initialize ESP module
  while ( status != WL_CONNECTED) {   // attempt to connect to WiFi network
    Serial.print("Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);    // Connect to WPA/WPA2 network
  }
  Serial.println("You're connected to the network");
  printWifiStatus(); // wifi 상태표시 및 딜레이
  Serial.println();
  Serial.println("Starting connection to server...");
  if (client.connect(host, 80)) {
    Serial.println("Connected to server");
    client.print("GET " + url + " HTTP/1.1\r\n" +  
                 "Host: " + host + "\r\n" +  
                 "Connection: close\r\n\r\n");
  }
}

void loop() {
  while (client.available()) {
    char c = client.read();
    Serial.write(c);
  }
  if (!client.connected()) {
    Serial.println();
    Serial.println("Disconnecting from server...");
    client.stop();
    while (true); // do nothing forevermore
  }
}

void printWifiStatus() {
  Serial.print("SSID: ");   // print the SSID of the network you're attached to
  Serial.println(WiFi.SSID());
  IPAddress ip = WiFi.localIP();  // print your WiFi shield's IP address
  Serial.print("IP Address: ");
  Serial.println(ip);
  long rssi = WiFi.RSSI();  // print the received signal strength
  Serial.print("Signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

 

아래는 상기 스케치로 수신하고 시리얼 모니터에 출력한 날씨 정보이다. JSON 형식으로 데이터가 들어온다. 

[WiFiEsp] Initializing ESP module
[WiFiEsp] Initilization successful - 1.1.1
Attempting to connect to WPA SSID: SK_WiFiGIGA40F7
[WiFiEsp] Connected to SK_WiFiGIGA40F7
You're connected to the network
SSID: SK_WiFiGIGA40F7
IP Address: 192.168.35.176
Signal strength (RSSI):-712 dBm

Starting connection to server...
[WiFiEsp] Connecting to api.openweathermap.org
Connected to server
HTTP/1.1 200 OK
Server: openresty
Date: Sat, 02 Nov 2019 17:32:46 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 436
Connection: close
X-Cache-Key: /data/2.5/weather?APPID=66e47f22f3570e6fd151ab5801a7&q=seoul,kr
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST

{"coord":{"lon":126.98,"lat":37.57},"weather":[{"id":701,"main":"Mist","description":"mist","icon":"50n"}],"base":"stations","main":{"temp":283.7,"pressure":1020,"humidity":100,"temp_min":281.15,"temp_max":286.15},"visibility":1200,"wind":{"speed":0.5,"deg":40},"clouds":{"all":75},"dt":1572715825,"sys":{"type":1,"id":5501,"country":"KR","sunrise":1572731897,"sunset":1572769974},"timezone":32400,"id":1835848,"name":"Seoul","cod":200}
Disconnecting from server...

 

수신된 날씨 정보는 JSON 형식이지만 JSON 라이브러리로 시도한 변환 과정에서 에러가 발생해 값을 추출할 수 없었다. 어차피 수신 데이터는 스트링이다.  스트링 클래스로 원하는 값을 추출해 보자.

WebClient_string_parsing.ino
0.00MB

#include "WiFiEsp.h"

#include <SoftwareSerial.h> 
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

char ssid[] = "SK_WiFiGIGA40F7";    // your network SSID (name)
char pass[] = "1712037218";         // your network password
int status = WL_IDLE_STATUS;        // the Wifi radio's status

// https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY
const char* host = "api.openweathermap.org";
const String url = "/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY";

WiFiEspClient client; // WiFiEspClient 객체 선언

// RSS 날씨 정보 저장 변수
String line = "";
bool weather_check = false;

// RSS 날씨 정보 수신에 소요되는 시간 확인용 변수
unsigned long int count_time = 0;
bool count_start = false;
int count_val = 0;

void get_weather() {
  Serial.println(F("Starting connection to server..."));
  if (client.connect(host, 80)) {
    Serial.println(F("Connected to server"));
    client.print("GET " + url + " HTTP/1.1\r\n" +  
                 "Host: " + host + "\r\n" +  
                 "Connection: close\r\n\r\n");
    weather_check = true;
    count_start = true;
  }
}

void setup() {
  Serial.begin(9600);  //시리얼모니터
  esp01.begin(9600);   //와이파이 시리얼
  WiFi.init(&esp01);   // initialize ESP module
  while ( status != WL_CONNECTED) {   // attempt to connect to WiFi network
    Serial.print(F("Attempting to connect to WPA SSID: "));
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);    // Connect to WPA/WPA2 network
  }
  Serial.println(F("You're connected to the network"));
  Serial.println();
  delay(1000);
  get_weather();
}

void check_client_available() {  // loop()함수 진입 및 스캔 테스트 코드
  if (count_start == true) {
    if (millis() - count_time >= 100) {
      count_time = millis();
      count_val++;
      Serial.print(".");
      if (count_val == 30) count_start = false;
    }
  }
}

void loop() {
  check_client_available();
  while(client.available()) { // 날씨 정보가 들어오는 동안
    char c = client.read();
    line += c;
  }
  if (weather_check == true) {
    if (!client.connected()) {   // 날씨정보 수신 종료됐으면
      Serial.println();
      Serial.println(F("Disconnecting from server..."));
      client.stop();
      Serial.println("weather data for parsing");
      parsing();
      weather_check = false;
    }
  }
} 

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

 

추출한 날씨 정보의 시리얼 모니터 출력 

[WiFiEsp] Initilization successful - 1.1.1
Attempting to connect to WPA SSID: SK_WiFiGIGA40F7
[WiFiEsp] Connected to SK_WiFiGIGA40F7
You're connected to the network

Starting connection to server...
[WiFiEsp] Connecting to api.openweathermap.org
Connected to server
.
Disconnecting from server...
weather data for parsing
description: mist
temp: 282.78
pressure: 1019
humidity: 100
.............................

 

2022년 6월 2일 업데이트

-> 2023년 2월 3일 확인한 바로는 기상청에서 RSS 방식의 날씨정보는 제공하지 않고 있습니다. 아래 코드는 참조만 하시기 바랍니다. 

 

* 2023년 4월 15일: 기상청에서 RSS기반으로 XML형식의 기상 정보도 다시 제공하는 것으로 확인했으나 전에 사용하던 아래 아두이노 코드를 이용해서는 데이터를 받을 수 없었습니다.

연결 주소
url: /wid/queryDFSRSS.jsp?zone=1159067000
host: www.kma.go.kr

기상청 응답
HTTP/1.1 404 Not Found
Server: openresty
Date: Fri, 14 Apr 2023 22:23:00 GMT
Content-Type: text/html
Content-Length: 150
Connection: close

<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>openresty</center>
</body>
</html>

상기와 같이 데이터를 받을 수 없다면 url을 전체 주소로 변경하여 시도해 보시기 바랍니다. 

url: /wid/queryDFSRSS.jsp?zone=1159067000

-> url: https://www.kma.go.kr/wid/queryDFSRSS.jsp?zone=5115061500

-> url: http://www.kma.go.kr/wid/queryDFSRSS.jsp?zone=5115061500 

 

url: /data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY

-> url: https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY

 

1. RSS 서비스 

https://www.weather.go.kr/w/index.do

 

기상청 날씨누리

기상청 날씨누리

www.weather.go.kr

기상청 날씨누리 페이지의 맨아래 아래와 같이 RSS 목록이 표시되어 있습니다. 

기상청 날씨누리 홈페이지 하단의 RSS 표시

 

2. RSS 주소 확인하기

날씨정보를 수신하고자 하는 지역을 순서대로 선택한 뒤 RSS 버튼을 클릭하면 전체 주소를 확인할 수 있습니다. 이 주소를 이용하여 아래 코드에 적용하시면 아두이노로 기상청 날씨정보를 수신할 수 있습니다.  

 

3. 수신한 날씨 정보에 대한 설명은 기상청에서 제공하는 아래 첨부파일을 참조하시기 바랍니다. 

dongnaeforecast_rss.pdf
0.17MB

 

아두이노 우노의 메모리가 작은 관계로 상기 코드 중 메모리를 절약할 수 있도록 host와 url 그리고 접속 코드를 PROGMEM과 F매크로를 사용하여 수정해 주었다. 

기상청

WebClient_string_parsing_find.ino
0.00MB

#include "WiFiEsp.h"

#include <SoftwareSerial.h> 
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 
int status = WL_IDLE_STATUS;        // the Wifi radio's status

// http://www.kma.go.kr/wid/queryDFSRSS.jsp?zone=1159068000
const char* host = "www.kma.go.kr";
const char url[]  PROGMEM = "/wid/queryDFSRSS.jsp?zone=1159068000";

WiFiEspClient client; // WiFiEspClient 객체 선언

// RSS 날씨 정보 저장 변수
String line0 = "";
String line1 = "";

uint8_t count = 0; // RSS 날씨 정보 분류 카운터

// RSS 날씨 정보 수신에 소요되는 시간 확인용 변수
unsigned long int count_time = 0;
bool count_start = false;
int count_val = 0;

void get_weather() {
  Serial.println(F("Starting connection to server..."));
  if (client.connect(host, 80)) {
    Serial.println(F("Connected to server"));
    String api = F("GET ");
    api += (const __FlashStringHelper *)url;
    api += F(" HTTP/1.1\r\nHost: ");
    api += host;
    api += F("\r\nConnection: close\r\n\r\n");
    client.print(api);
    count_start = true;
  }
}

void setup() {
  Serial.begin(9600);  //시리얼모니터
  esp01.begin(9600);   //와이파이 시리얼
  WiFi.init(&esp01);   // initialize ESP module
  while ( status != WL_CONNECTED) {   // attempt to connect to WiFi network
    Serial.print(F("Attempting to connect to WPA SSID: "));
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);    // Connect to WPA/WPA2 network
  }
  Serial.println(F("You're connected to the network"));
  Serial.println();
  delay(1000);
  get_weather();
}

void check_client_available() {  // loop()함수 진입 및 스캔 테스트 코드
  if (count_start == true) {
    if (millis() - count_time >= 100) {
      count_time = millis();
      count_val++;
      Serial.print(".");
      if (count_val == 30) count_start = false;
    }
  }
}

void loop() {
  check_client_available();
  if (client.available()) { // 날씨 정보가 들어오는 동안
    bool check = client.find("<header>");
    if (check) {
      while (client.available()) {
        char c = client.read();
        if (c == '<') count++;
        if (count <= 50) line0 += c;
        else if (count > 50 && count <= 90) line1 += c;
        else if (count > 90) break; // 중간에 빠져나가고 
      }
    }
  }
  if (count != 0) { // 수신 값 있으면 
    while(client.available()) {  // 나머지 날씨정보 버리기
      char c = client.read(); 
    }
    if (!client.connected()) {   // 날씨정보 수신 종료됐으면
      Serial.println();
      Serial.println(F("Disconnecting from server..."));
      client.stop();
      Serial.println(line0);
      Serial.println(line1);
    }
    parsing();
    count = 0;
  } 
}

// 온도 저장 변수
float temp0;
float temp1;

void parsing() {
  String announce_time;
  int tm_start= line0.indexOf(F("<tm>")); // "<tm>"문자가 시작되는 인덱스 값('<'의 인덱스)을 반환한다. 
  int tm_end= line0.indexOf(F("</tm>"));  
  announce_time = line0.substring(tm_start + 4, tm_end); // +4: "<tm>"스트링의 크기 4바이트, 4칸 이동
  Serial.print(F("announce_time: ")); Serial.println(announce_time);
  String hour;
  int hour_start= line0.indexOf(F("<hour>"));
  int hour_end= line0.indexOf(F("</hour>"));
  hour = line0.substring(hour_start + 6, hour_end);
  Serial.print(F("hour: ")); Serial.println(hour);
  String temp;
  int temp_start= line0.indexOf(F("<temp>"));
  int temp_end= line0.indexOf(F("</temp>"));
  temp = line0.substring(temp_start + 6, temp_end);
  Serial.print(F("temp: ")); Serial.println(temp);
  temp0 = temp.toFloat();   // 자료형 변경 String -> float
  Serial.print(F("temp0: ")); Serial.println(temp0);
  String wfEn;
  int wfEn_start= line0.indexOf(F("<wfEn>"));
  int wfEn_end= line0.indexOf(F("</wfEn>"));
  wfEn = line0.substring(wfEn_start + 6, wfEn_end);
  Serial.print(F("weather: ")); Serial.println(wfEn);
  line0 = ""; // 스트링 변수 line0 데이터 추출 완료 
  Serial.println();
  hour_start= line1.indexOf(F("<hour>"));
  hour_end= line1.indexOf(F("</hour>"));
  hour = line1.substring(hour_start + 6, hour_end);
  Serial.print(F("hour: ")); Serial.println(hour);
  temp_start= line1.indexOf(F("<temp>"));
  temp_end= line1.indexOf(F("</temp>"));
  temp = line1.substring(temp_start + 6, temp_end);
  Serial.print(F("temp: ")); Serial.println(temp);
  temp1 = temp.toFloat();   // 자료형 변경 String -> float
  Serial.print(F("temp1: ")); Serial.println(temp1);
  wfEn_start= line1.indexOf(F("<wfEn>"));
  wfEn_end= line1.indexOf(F("</wfEn>"));
  wfEn = line1.substring(wfEn_start + 6, wfEn_end);
  Serial.print(F("weather: ")); Serial.println(wfEn);
  line1 = ""; // 스트링 변수 line1 데이터 추출 완료 
}

 

 

- OPEN WEATHER MAP

open_weather_API_JSON_parsing.ino
0.00MB

#include "WiFiEsp.h"

#include <SoftwareSerial.h> 
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 
int status = WL_IDLE_STATUS;        // the Wifi radio's status

// https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY
const char* host = "api.openweathermap.org";
const char url[] PROGMEM = "/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY";

WiFiEspClient client; // WiFiEspClient 객체 선언

// RSS 날씨 정보 저장 변수
String line = "";
bool weather_check = false;

// RSS 날씨 정보 수신에 소요되는 시간 확인용 변수
unsigned long int count_time = 0;
bool count_start = false;
int count_val = 0;

void get_weather() {
  Serial.println(F("Starting connection to server..."));
  if (client.connect(host, 80)) {
    Serial.println(F("Connected to server"));
    String api = F("GET ");
    api += (const __FlashStringHelper *)url;
    api += F(" HTTP/1.1\r\nHost: ");
    api += host;
    api += F("\r\nConnection: close\r\n\r\n");
    client.print(api);
    weather_check = true;
    count_start = true;
  }
}

void setup() {
  Serial.begin(9600);  //시리얼모니터
  esp01.begin(9600);   //와이파이 시리얼
  WiFi.init(&esp01);   // initialize ESP module
  while ( status != WL_CONNECTED) {   // attempt to connect to WiFi network
    Serial.print(F("Attempting to connect to WPA SSID: "));
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);    // Connect to WPA/WPA2 network
  }
  Serial.println(F("You're connected to the network"));
  Serial.println();
  delay(1000);
  get_weather();
}

void check_client_available() {  // loop()함수 진입 및 스캔 테스트 코드
  if (count_start == true) {
    if (millis() - count_time >= 100) {
      count_time = millis();
      count_val++;
      Serial.print(".");
      if (count_val == 30) count_start = false;
    }
  }
}

bool receive_data = false;

void loop() {
  check_client_available();
  while(client.available()) { // 날씨 정보가 들어오는 동안
    char c = client.read();
    if(c == '{') receive_data = true; // { 데이터 저장 시작, '{'는 저장 안함
    else if (receive_data == true) line += c;
  }
  if (weather_check == true) {
    if (!client.connected()) {   // 날씨정보 수신 종료됐으면
      receive_data = false;
      Serial.println();
      Serial.println(F("Disconnecting from server..."));
      client.stop();
      Serial.println(line);
      Serial.println(F("weather data for parsing"));
      String description = json_parser(line, F("description"));
      Serial.print(F("description: ")); Serial.println(description);
      String pressure = json_parser(line, F("pressure"));
      Serial.print(F("pressure: ")); Serial.println(pressure);
      String wind = json_parser(line, F("speed"));
      Serial.print(F("wind: ")); Serial.println(wind);
      String Temp = json_parser(line, F("temp"));
      Serial.print(F("Temp: ")); Serial.println(Temp);
      line = "";
      weather_check = false;
    }
  }
} 

String json_parser(String s, String a) { 
  String val;
  if (s.indexOf(a) != -1) {
    int st_index = s.indexOf(a);
    int val_index = s.indexOf(':', st_index);
    if (s.charAt(val_index + 1) == '"') {
      int ed_index = s.indexOf('"', val_index + 2);
      val = s.substring(val_index + 2, ed_index);
    }
    else {
      int ed_index = s.indexOf(',', val_index + 1);
      val = s.substring(val_index + 1, ed_index);
    }
  } 
  else {
    Serial.print(a); Serial.println(F(" is not available"));
  }
  return val;
}

 

WiFiEsp 라이브러리를 사용하지 않고 AT command를 이용하여 날씨 정보 받기

: 라이브러리를 사용하지 않음으로써 메모리를 좀 더 확보할 수 있다. 

기상청

스케치는 프로그램 저장 공간 13142 바이트(40%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 437바이트(21%)를 사용, 1611바이트의 지역변수가 남음. 최대는 2048 바이트.

Serial_basic_esp01_AT_weather_data_kor.ino
0.01MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";
String ap = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ap = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ap);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

void setup() {
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("CWQIF")); // disconnect AP if connected
  sendData(F("CWQAP")); // disconnect wifi if connected
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
}

// http://www.kma.go.kr/wid/queryDFSRSS.jsp?zone=1159068000
const char host[] PROGMEM = "www.kma.go.kr";
const char url[]  PROGMEM = "/wid/queryDFSRSS.jsp?zone=1159068000";

bool OK200 = false;
bool sentREQ = false;
bool gotWeather = false;

void get_weather() {
  if (!sentREQ) {
    Serial.println(F("Starting connection to server..."));
    bool connectOK = false;
    String cip = F("AT+CIPSTART=3,"); // connection ID 3 (ID: 0 ~ 4)
    cip += '"'; cip += F("TCP"); cip += '"';
    cip += ','; cip += '"';
    cip += (const __FlashStringHelper *)host;
    cip += '"'; cip += ',';
    cip += F("80"); // port number
    cip += F("\r\n");
    esp01.print(cip);
    unsigned long int time = millis();
    while (time+2000 > millis()) {
      cip = esp01.readStringUntil('\n');
      if (cip.indexOf(F("OK")) != -1) { connectOK = true; Serial.println(F("OK")); break; }
      else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
    }
    if (connectOK) {
      Serial.println(F("Connected to server"));
      String temp = F("GET ");
      temp += (const __FlashStringHelper *)url;
      temp += F(" HTTP/1.1\r\nHost: ");
      temp += (const __FlashStringHelper *)host;
      temp += F("\r\nConnection: close\r\n\r\n");
      cip = F("AT+CIPSEND=");
      cip += 3; // connection ID 3 (ID: 0 ~ 4)
      cip += ',';
      cip += temp.length();
      cip += F("\r\n");
      esp01.print(cip);
      time = millis();
      while (time+2000 > millis()) {
        cip = esp01.readStringUntil('\n');
        if (cip.indexOf(F("OK")) != -1) { Serial.println(F("OK")); break; }
        else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
      }
      esp01.print(temp);
      sentREQ = true;
      gotWeather = true;
    }
  }
}

// RSS 날씨 정보 저장 변수
// RSS 날씨 정보 저장 변수
String line0 = "";
String line1 = "";

uint8_t count = 0; // RSS 날씨 정보 분류 카운터
uint8_t tryCount = 0;

// 온도 저장 변수
float temp0;
float temp1;

void parsing() {
  String announce_time;
  int tm_start= line0.indexOf(F("<tm>")); // "<tm>"문자가 시작되는 인덱스 값('<'의 인덱스)을 반환한다. 
  int tm_end= line0.indexOf(F("</tm>"));  
  announce_time = line0.substring(tm_start + 4, tm_end); // +4: "<tm>"스트링의 크기 4바이트, 4칸 이동
  Serial.print(F("announce_time: ")); Serial.println(announce_time);
  String hour;
  int hour_start= line0.indexOf(F("<hour>"));
  int hour_end= line0.indexOf(F("</hour>"));
  hour = line0.substring(hour_start + 6, hour_end);
  Serial.print(F("hour: ")); Serial.println(hour);
  String temp;
  int temp_start= line0.indexOf(F("<temp>"));
  int temp_end= line0.indexOf(F("</temp>"));
  temp = line0.substring(temp_start + 6, temp_end);
  Serial.print(F("temp: ")); Serial.println(temp);
  temp0 = temp.toFloat();   // 자료형 변경 String -> float
  Serial.print(F("temp0: ")); Serial.println(temp0);
  String wfEn;
  int wfEn_start= line0.indexOf(F("<wfEn>"));
  int wfEn_end= line0.indexOf(F("</wfEn>"));
  wfEn = line0.substring(wfEn_start + 6, wfEn_end);
  Serial.print(F("weather: ")); Serial.println(wfEn);
  line0 = ""; // 스트링 변수 line0 데이터 추출 완료 
  Serial.println();
  hour_start= line1.indexOf(F("<hour>"));
  hour_end= line1.indexOf(F("</hour>"));
  hour = line1.substring(hour_start + 6, hour_end);
  Serial.print(F("hour: ")); Serial.println(hour);
  temp_start= line1.indexOf(F("<temp>"));
  temp_end= line1.indexOf(F("</temp>"));
  temp = line1.substring(temp_start + 6, temp_end);
  Serial.print(F("temp: ")); Serial.println(temp);
  temp1 = temp.toFloat();   // 자료형 변경 String -> float
  Serial.print(F("temp1: ")); Serial.println(temp1);
  wfEn_start= line1.indexOf(F("<wfEn>"));
  wfEn_end= line1.indexOf(F("</wfEn>"));
  wfEn = line1.substring(wfEn_start + 6, wfEn_end);
  Serial.print(F("weather: ")); Serial.println(wfEn);
  line1 = ""; // 스트링 변수 line1 데이터 추출 완료 
}

bool header = false;

void loop() {
  if (!ip.startsWith(F("0"))) { // 공유기로부터 IP주소를 할당받은 경우
    if (!gotWeather) {
      if (tryCount < 3) { get_weather(); tryCount++; }
    }
  }
  if (sentREQ) { // 날씨 정보가 들어오는 동안
    if (!OK200) {
      if (esp01.find('+')) {
        line0 = esp01.readStringUntil('\n');
        if (line0.indexOf(F("200")) != -1) OK200 = true;
        line0 = "";
      } else { // gotWeather = true; -> 날씨 작업 종료 후 나머지 데이터 삭제
        line0 = esp01.readStringUntil('\n');
        if (line0.endsWith(F("D"))) { 
          if (line0.endsWith(F("CLOSED"))) sentREQ = false; // HTTP connection closed
        }
      }
    } else {
      if (!header) {
        line0 = esp01.readStringUntil('\n');
        if (line0.indexOf(F("<header>")) != -1) { header = true; line0 = ""; }
      } else {
        bool finish = false;
        while (esp01.available()) {
          char c = esp01.read();
          if (c == '<') count++;
          if (count <= 50) line0 += c;
          else if (count > 50 && count <= 90) line1 += c;
          else if (count > 90) { finish = true; break; }
        }
        if (finish) {
          Serial.println(F("Finished Reading"));
          Serial.println(line0);
          Serial.println(line1);
          parsing();
          gotWeather = true; // 날씨 작업 종료
          count = 0; header = false; OK200 = false; tryCount = 0; // 초기화 sentREQ = false; 
        }
      }
    }
  } else { // 날씨 이외의 경우
    if (esp01.available()) {
      line0 = esp01.readStringUntil('\n');
      Serial.println(line0);
    }
  }
}

 

OPEN WEATER MAP

스케치는 프로그램 저장 공간 10142 바이트(31%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 407바이트(19%)를 사용, 1641바이트의 지역변수가 남음. 최대는 2048 바이트.

Serial_basic_esp01_AT_weather_data_open.ino
0.01MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";
String ap = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ap = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ap);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

void setup() {
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("CWQIF")); // disconnect AP if connected
  sendData(F("CWQAP")); // disconnect wifi if connected
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
}

String json_parser(String s, String a) { 
  String val;
  if (s.indexOf(a) != -1) {
    int st_index = s.indexOf(a);
    int val_index = s.indexOf(':', st_index);
    if (s.charAt(val_index + 1) == '"') {
      int ed_index = s.indexOf('"', val_index + 2);
      val = s.substring(val_index + 2, ed_index);
    }
    else {
      int ed_index = s.indexOf(',', val_index + 1);
      val = s.substring(val_index + 1, ed_index);
    }
  } 
  else {
    Serial.print(a); Serial.println(F(" is not available"));
  }
  return val;
}

// OPEN WEATHER MAP
// https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY
const char host[] PROGMEM = "api.openweathermap.org";
const char url[]  PROGMEM = "/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY"; // YOUR_API_KEY 수정

bool OK200 = false;
bool sentREQ = false;
bool gotWeather = false;

void get_weather() {
  Serial.println(F("Starting connection to server..."));
  bool connectOK = false;
  String cip = F("AT+CIPSTART=3,"); // connection ID 3 (ID: 0 ~ 4)
  cip += '"'; cip += F("TCP"); cip += '"';
  cip += ','; cip += '"';
  cip += (const __FlashStringHelper *)host;
  cip += '"'; cip += ',';
  cip += F("80"); // port number
  cip += F("\r\n");
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { connectOK = true; Serial.println(F("OK")); break; }
    else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
  }
  if (connectOK) {
    Serial.println(F("Connected to server"));
    String temp = F("GET ");
    temp += (const __FlashStringHelper *)url;
    temp += F(" HTTP/1.1\r\nHost: ");
    temp += (const __FlashStringHelper *)host;
    temp += F("\r\nConnection: close\r\n\r\n");
    cip = F("AT+CIPSEND=");
    cip += 3; // connection ID 3 (ID: 0 ~ 4)
    cip += ',';
    cip += temp.length();
    cip += F("\r\n");
    esp01.print(cip);
    time = millis();
    while (time+2000 > millis()) {
      cip = esp01.readStringUntil('\n');
      if (cip.indexOf(F("OK")) != -1) { Serial.println(F("OK")); break; }
      else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
    }
    esp01.print(temp);
    sentREQ = true;
    gotWeather = true;
  }
}

// RSS 날씨 정보 저장 변수
String line = "";
uint8_t tryCount = 0;

void loop() {
  if (!ip.startsWith(F("0"))) { // 공유기로부터 IP주소를 할당받은 경우
    if (!gotWeather) {
      if (tryCount < 3) { get_weather(); tryCount++; }
    }
  }
  while (esp01.available()) { 
    line = esp01.readStringUntil('\n');
    Serial.println(line);
    if (sentREQ) {
      if (!OK200) {
        if (line.startsWith(F("+"))) {
          if (line.indexOf(F("200")) != -1) OK200 = true;
          else { OK200 = false; gotWeather = false; }
        }
      } else {
        if (line.startsWith(F("{"))) {
          Serial.println(line);
          Serial.println(F("weather data for parsing"));
          String description = json_parser(line, F("description"));
          Serial.print(F("description: ")); Serial.println(description);
          String pressure = json_parser(line, F("pressure"));
          Serial.print(F("pressure: ")); Serial.println(pressure);
          String wind = json_parser(line, F("speed"));
          Serial.print(F("wind: ")); Serial.println(wind);
          String Temp = json_parser(line, F("temp"));
          Serial.print(F("Temp: ")); Serial.println(Temp);
          line = "";
          gotWeather = true; // 날씨 작업 종료
          tryCount = 0; OK200 = false; sentREQ = false; // 초기화
        }
      }
    } else {
      Serial.println(line);
    }
  }
}

 

5 day weather forecast로 3시간 단위의 24시간 기상예보를 받아보겠습니다.

 

주소형식

http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={API key}

 

아래는 오픈웨더맵에서 3시간 간격으로 전송하는 데이터중 필요한 데이터 패키지 8개의 예시입니다. 

{"dt":1675404000,"main":{"temp":274.44,"feels_like":271.85,"temp_min":273.9,"temp_max":274.44,"pressure":1024,"sea_level":1024,"grnd_level":1019,"humidity":26,"temp_kf":0.54},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"clouds":{"all":84},"wind":{"speed":2.31,"deg":278,"gust":2.68},"visibility":10000,"pop":0,"sys":{"pod":"d"},"dt_txt":"2023-02-03 06:00:00"},

{"dt":1675414800,"main":{"temp":273.49,"feels_like":270.68,"temp_min":272.88,"temp_max":273.49,"pressure":1024,"sea_level":1024,"grnd_level":1020,"humidity":31,"temp_kf":0.61},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"clouds":{"all":31},"wind":{"speed":2.36,"deg":272,"gust":4.01},"visibility":10000,"pop":0,"sys":{"pod":"n"},"dt_txt":"2023-02-03 09:00:00"},

{"dt":1675425600,"main":{"temp":272.79,"feels_like":272.79,"temp_min":272.79,"temp_max":272.79,"pressure":1024,"sea_level":1024,"grnd_level":1020,"humidity":40,"temp_kf":0},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"clouds":{"all":0},"wind":{"speed":0.97,"deg":235,"gust":1.98},"visibility":10000,"pop":0,"sys":{"pod":"n"},"dt_txt":"2023-02-03 12:00:00"},

{"dt":1675436400,"main":{"temp":272.51,"feels_like":270.73,"temp_min":272.51,"temp_max":272.51,"pressure":1024,"sea_level":1024,"grnd_level":1021,"humidity":40,"temp_kf":0},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}],"clouds":{"all":11},"wind":{"speed":1.46,"deg":300,"gust":2.73},"visibility":10000,"pop":0,"sys":{"pod":"n"},"dt_txt":"2023-02-03 15:00:00"},

{"dt":1675447200,"main":{"temp":272,"feels_like":269.95,"temp_min":272,"temp_max":272,"pressure":1024,"sea_level":1024,"grnd_level":1021,"humidity":36,"temp_kf":0},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"clouds":{"all":7},"wind":{"speed":1.58,"deg":299,"gust":4.75},"visibility":10000,"pop":0,"sys":{"pod":"n"},"dt_txt":"2023-02-03 18:00:00"},

{"dt":1675458000,"main":{"temp":271.63,"feels_like":271.63,"temp_min":271.63,"temp_max":271.63,"pressure":1024,"sea_level":1024,"grnd_level":1021,"humidity":38,"temp_kf":0},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"clouds":{"all":0},"wind":{"speed":0.51,"deg":294,"gust":1.15},"visibility":10000,"pop":0,"sys":{"pod":"n"},"dt_txt":"2023-02-03 21:00:00"},

{"dt":1675468800,"main":{"temp":272.39,"feels_like":272.39,"temp_min":272.39,"temp_max":272.39,"pressure":1025,"sea_level":1025,"grnd_level":1022,"humidity":41,"temp_kf":0},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"clouds":{"all":3},"wind":{"speed":0.56,"deg":181,"gust":0.97},"visibility":10000,"pop":0,"sys":{"pod":"d"},"dt_txt":"2023-02-04 00:00:00"},

{"dt":1675479600,"main":{"temp":275.6,"feels_like":272.63,"temp_min":275.6,"temp_max":275.6,"pressure":1025,"sea_level":1025,"grnd_level":1022,"humidity":51,"temp_kf":0},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"clouds":{"all":6},"wind":{"speed":2.95,"deg":258,"gust":5.38},"visibility":10000,"pop":0,"sys":{"pod":"d"},"dt_txt":"2023-02-04 03:00:00"},

 

날씨 코드 명세

temp: 온도. 단위 기본값: 켈빈, 미터법: 섭씨, 임페리얼: 화씨.
feel_like: 이 온도 매개변수는 날씨에 대한 인간의 인식을 설명합니다. 단위 기본값: 켈빈, 미터법: 섭씨, 임페리얼: 화씨.
temp_min: 계산 시점의 최저 온도. 이것은 최소 예상 온도(대규모 대도시 및 도시 지역 내)이며 이 매개변수를 선택적으로 사용합니다. 단위 기본값: 켈빈, 미터법: 섭씨, 임페리얼: 화씨.
temp_max: 계산 시점의 최대 온도. 이것은 최대 예측 온도(대형 대도시 및 도시 지역 내)이며 이 매개변수를 선택적으로 사용합니다. 단위 기본값: 켈빈, 미터법: 섭씨, 임페리얼: 화씨.
pressure: 기본적으로 해수면의 대기압, hPa
sea_level: 해수면의 대기압, hPa
grnd_level: 지면의 대기압, hPa
humidity: 습도, %
weather.id: 날씨 조건 id :: https://openweathermap.org/weather-conditions

weather.main: 날씨 매개변수 그룹(비, 눈, 극한 등)

weather.description: 그룹 내 기상 조건입니다.
weather.icon: 날씨 아이콘 ID
cloud.all: 구름 분포, %
wind.speed: 풍속. 단위 기본값: 미터/초, 미터법: 미터/초, 임페리얼: 마일/시간.
wind.deg: 풍향, 정도(기상학적)
wind.gust: 돌풍. 단위 기본값: 미터/초, 미터법: 미터/초, 임페리얼: 마일/시간
visibility: 평균 가시성, 미터. 가시성의 최댓값은 10km입니다.
pop: 강수 확률. 매개변수의 값은 0과 1 사이에서 변합니다. 여기서 0은 0%, 1은 100%, 0.7은 70%입니다.
rain.3h: 지난 3시간 동안의 비의 양, mm :: 예보가 아님
snow.3h: 지난 3시간 동안의 적설량 :: 예보가 아님
sys.pod 하루 중 일부(n - 밤, d - 낮)
dt_txt: 데이터 예측 시간, ISO, UTC

 

이중 temp, humidity, weather.main, description, pop을 파싱 하겠습니다.

 

오픈웨더맵에서 전송한 날씨 데이터는 문자 '['에서 시작합니다. 문자 '[' 있으면 데이터를 수신한 것으로 확인되어 파싱을 진행합니다. 

while (client.available()) { check = client.find("["); if (check) break; }
Serial.println(F("OK"));

파싱코드

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) { // 8개의 3시간 단위 날씨 정보를 모두 받지 않은 동안에
        for (uint8_t i = 0; i < 3; i++) check = client.find(":"); // temp 값 앞으로 이동
        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(":"); // humidity 값 앞으로 이동
        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(":"); // main 값 앞으로 이동
        cc = client.read(); cc = 'a'; main = "";                  // 문자 '"' 삭제, 변수 초기화
        while (cc != '"') { cc = client.read(); if (cc != '"') main += cc; } // 문자 '"' 까지 문자열 저장
        check = client.find(":");                                 // description 값 앞으로 이동
        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(":"); // pop 값 앞으로 이동
        cc = 'a'; line0 = "";                                     // 변수 초기화
        while (cc != ',') { cc = client.read(); if (cc != ',') line0 += cc; } // 문자 ',' 까지 문자열 저장
        rain = line0.toFloat()*100;                               // 강수확율 소수 문자열 정수로 변환
        check = client.find("_");                                 // 날짜코드로 이동 "dt_txt"
        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("}");                                  // 3시간 단위 날씨 정보 종료
        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) {                                         // 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; } // 5번 실패 종료
      delay(100); 
    }
  } // 클라이언트 연결이 종료되면
  count = 0; // 초기화
}

 

OPEN_Weather_5days_Parsing.zip

OPEN_Weather_5days_Parsing.zip
0.00MB

스케치는 프로그램 저장 공간 17320 바이트(53%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 608바이트(29%)를 사용, 1440바이트의 지역변수가 남음. 최대는 2048 바이트.

 

시리얼 모니터 출력 데이터

--------------------
Starting connection to server...
[WiFiEsp] Connecting to api.openweathermap.org
Connected to server
OK
9: 275.08,28,Clear,clear sky,0%
12: 273.85,34,Clear,clear sky,0%
15: 272.26,37,Clouds,scattered clouds,0%
18: 271.64,32,Clouds,few clouds,0%
21: 271.36,38,Clear,clear sky,0%
0: 272.12,42,Clear,clear sky,0%
3: 275.42,50,Clear,clear sky,0%
6: 276.24,38,Clear,clear sky,0%

 

 

 

 

관련 글

[arduino] - ESP8266 / ESP32 - HTTPClient.h 라이브러리 이용 날씨 정보 받기, RSS / API

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

 

 

+ Recent posts