WiFi 를 통한 아두이노 활용(7) : 날씨 정보 가져오기 #4

2017. 7. 21. 19:20

Arduino/Wireless

아두이노와 무선인터넷을 통해 날씨 정보 가져오기 네번째

이전 글에서 XML Paring에 대해 간략하게 처리할 수 있는 프로그램을 완성했고 이번 글에선 실제 데이터를 추출하여 출력하는 소스를 작성하겠습니다.

<country>KR</country>

OpenWeatherMap에서 전송된 응답 메시지에서 검색된 도시의 국가명이 들어 있는 Elements입니다. 유일하게 Text Contents가 존재하는 부분이기도 하구요. 이 부분을 가장 먼저 출력해 보도록 하겠습니다.

그럼, 프로그램 상 언제 출력하면 될까요? start tag인 <country>가 완성된 후에 text인 KR의 저장이 시작되고 end tag인 </country>가 시작 되어야만 text의 저장이 완료되니 이 시점 이후에 출력할 수 있는 준비가 됩니다.

가능한 시점이 몇가지 있을 듯 하지만 가장 정확한 데이터를 출력하는 길은, <country> 태그와 </country>가 각각 start tag, end tag로서 짝을 이루는 지를 확인하고 출력하는 것일듯 합니다. 따라서 한 엘리먼츠가 끝난 후에 데이터의 처리를 하는 코드가 오도록 코드를 구성하고자 하는데, 이전 글에서 작성한 소스를 보시면 이미 그대로 구현하였습니다.

if (currentTag.startsWith("</")) {
        flagStartTag = false;
        endTag = currentTag;  // store end Tag
        Serial.print(currentData);
        Serial.println(endTag);
        currentData = ""; 
      } else {

이전 글에서 완성한 소스입니다. 위 코드는 태그가 하나 완성된 후 검사해서 엔드 태그인지 확인했을 때의 코드입니다. 주황색 코드가 텍스트 데이터를 출력하는 부분인데 이 곳이 바로 위에서 제가 말한 시점입니다. 현재는 currentData 즉 텍스트 데이터가 있건 없건 무조건 출력하도록 돼 있습니다. 조건문을 넣어서 원하는 데이터만 확인 후에 출력하도록 수정하겠습니다.

if (currentTag.startsWith("</")) {
        flagStartTag = false;
        endTag = currentTag;  // store end Tag
        if (startTag.indexOf("country") != -1) {
          if (endTag.indexOf("country") != -1) {
            Serial.print("Country : ");
            Serial.println(currentData);
          }
        }
        currentData = ""; 
      } else {
        flagStartTag = true; 
        startTag = currentTag; // store start Tag
      }

수정한 내용은 위와 같습니다. 4 ~ 7행 부분이 국가명 KR을 출력하는 부분으로, start tag에 country 라는 문자열이 있으면, 그리고 end tag에도 역시 country 문자열이 있으면 currentData를 출력하는 코드입니다.

if (startTag.indexOf("country") != -1) {

.indexOf() 함수는 인수로 주어진 문자열이 String변수내에 존재하면 그 인덱스를 정수값으로 반환하고 없다면 -1을 반환하는 함수입니다. .startsWith() 함수와 마찬가지로 String object type의 멤버 함수입니다.

위 코드들은 IF문을 중첩으로 사용해서 start tag와 end tag를 둘 다 체크하고 있는데, 실제로는 start tag만 검사해도 결과는 마찬가지입니다. 하지만, 나중에 훨씬 더 복잡한 XML을 처리할 때가 온다면 되도록 유효한 값인지 좀더 확인하는 과정이 필요할 것입니다.

컨텐츠 데이터 외에 다른 출력문은 모두 삭제했습니다.

결과 화면입니다. 응답메시지는 이제 출력하지 않도록 했고, 국가명만 제대로 출력된 것을 확인할 수 있습니다.

City Name 출력하기

이제 city 태그에서 city name을 추출해서 출력해보겠습니다. city tag는 데이터가 들어있는 첫번째 start 태그입니다.

<city id="1835847" name="Seoul-teukbyeolsi">
		<coord lon="127" lat="37.58"></coord>
		<country>KR</country>
		<sun rise="2017-07-17T20:24:48" set="2017-07-18T10:51:18"></sun>
</city>

OpenWeatherMap의 XML 데이터는 대부분의 데이터를 start tag의 attributes를 통해서 제공합니다. 미세먼지 정보를 제공하는 AirKorea의 XML문은 모든 정보가 텍스트 컨텐츠를 통해서 제공하는 것과는 다른 모습입니다. 텍스트 컨텐츠 방식은 위에서 country명 KR을 출력하듯이 비교적 쉽게 할 수 있는데, 어트리뷰츠 방식은 많이 번거롭네요!

start tag에 있는 데이터를 추출하기 위해선 end tag까지 나온 후에 처리할 수 없습니다. country때와는 다르죠! 위 XML문을 보시면 <city>와 <coord>처럼 start tag가 연속으로 나올 수 있기 때문입니다. 그래서, start tag의 처리는 start tag가 완성된 후에 바로 하도록 구성했습니다.

if (currentTag.startsWith("</")) {
        flagStartTag = false;
        endTag = currentTag;  // store end Tag
        if (startTag.indexOf("country") != -1) {
          if (endTag.indexOf("country") != -1) {
            Serial.print("Country : ");
            Serial.println(currentData);
          }
        }
        currentData = ""; 
      } else {
        flagStartTag = true;                
        startTag = currentTag; // store start Tag
        startTagProcessing();  // Start Tag에 대한 처리 함수
      }

start tag에 대한 처리는 코딩 양이 좀 많을 듯 해서 따로 함수를 하나 만들어 쓰도록 하겠습니다. start tag 하나가 완성되면 변수 startTag에 그 태그를 저장하고 startTagProcessing() 함수를 호출합니다. 처리를 마치면 함수가 끝나고 돌아와서 다시 다른 태그를 입력 받기 위해 처음으로 돌아 갑니다.

이제 부터 start tag 처리 함수인 startTagProcessing() 함수를 만들어 보겠습니다. loop() 함수가 끝나는 곳 아래쪽에 코딩하면 됩니다.

void startTagProcessing() {
}

시작 태그 처리함수의 기본 구조입니다. 별다른 리턴값은 필요 없기 때문에 void형으로 선언하였고, 역시 인수도 넘겨줄 필요가 없습니다. 여기서 필요한 startTag 변수는 이미 setup() 함수 이전에 선언한 전역변수이기 때문에 각각의 함수 내에서 자유롭게 읽고 쓸수 있으니까요!

이 함수가 모든 start tag를 처리하는 건 아닙니다. 추출하기위해 필요한 start tag만 선택해서 처리해야 합니다. 따라서 처리를 원하는 start tag가 들어 왔는지 체크하는 부분이 각 start tag 마다 필요합니다. 우선, city 태그만 처리하겠습니다.

void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    Serial.println("City : ");
  }
}

테스트용으로 간단하게 작성하였습니다. 함수가 시작되면, startTag의 값 즉 현재 start tag가 "<city" 문자열로 시작하는지 묻습니다. city 태그를 찾기 위해서 입니다. String.startsWith() 함수는 이미 써봤던 함수죠. start tag인지 end tag인지 알아보기 위해 사용했었습니다. 위쪽 소스에서도 보이네요! startTag 값을 비교해서 "<city" 문자열로 시작하면 "City : " 를 출력합니다.

결과 화면입니다. 위와 같이 잘 출력 되었습니다. 이제 <city> 태그는 찾았으니 그 속의 어트리뷰트들을 찾을 차례입니다.

<city id="1835847" name="Seoul-teukbyeolsi">

city tag의 전체 코드입니다. 여기서 id 값은 검색을 위한 용도이지 출력할 필요는 없을 듯해서 name 어트리뷰트만 추출하겠습니다. 우선, 해당 태그내에 name 이 있는지 찾아야 겠죠?

int attribName = startTag.indexOf("name=");

"name" 어트리뷰트가 있는지 확인하기 위한 명령입니다. 위 변수 attribName의 값이 -1이면 찾지 못한 것이고 정수값이면 그 숫자에 해당하는 위치(몇 번째....)에 "name=" 문자열이 존재하는 것입니다.

String.indexOf("name=") 함수도 이 글 처음 부분에서 사용했었습니다. 인수로 주어진 문자열이 해당 String형 변수에 존재하는지 찾는 함수입니다. .startsWith() 함수는 어떤 문자열이 String 시작 부분에 있는지 찾고, .indexOf() 함수는 시작 부분이든 어디든 String 안에 그 내용이 있는지 찾으며, endsWith() 함수는 String의 끝 부분이 그 문자열인지 찾는 함수입니다.

.startsWith().endsWith() 함수는 시작이나 끝이 그 문자열이 맞는지 아닌지만 확인하면 되므로 리턴값도 true, false 둘 중에 하나입니다. 그래서 두 함수는 결과값을 그대로 IF문 등의 조건식에 사용할 수 있습니다. 즉, 아래 두가지 표현은 의미가 같습니다.

if (startTag.startsWith("<city"))
if (startTag.startsWith("<city") == true)

하지만, indexOf() 함수는 리턴값이 정수형 숫자이거나 아니면 -1 입니다. String형 변수에 찾으려는 문자열이 있다면 그 문자열의 위치를 정수형 인덱스값으로 리턴하고 만약 찾지 못한다면 -1 값을 리턴합니다. 정수형 인덱스값은 그 문자열이 몇 번째에 위치하는지 알려주는 숫자값입니다. 아래 예를 참고 하세요.

String example = "abc123";
int aaa = example.indexOf("abc");   // aaa = 0, 문자열의 시작은 0.
int bbb = example.indexOf("123");  // bbb = 3, 3은 0부터 시작했을 때 네번째 문자.
int ccc = example.indexOf("xyz");   // ccc = -1, 찾을 수 없음.

문자열의 인덱스는 0부터 시작합니다. 즉, 위 예에서 "a"는 0번째 위치하고 있고, indexOf("abc")는 첫번째 문자인 "a"의 인덱스값을 리턴하기 때문에, 변수 aaa는 숫자 0을 갖게 됩니다. 마찬가지로 indexOf("123")은 문자 "1"의 인덱스값을 리턴하는데, 문자 "1"은 네 번째 문자입니다. 하지만 결과는 4가 아니고 3입니다. 0부터 시작하기 때문이죠. "xyz"는 찾을 수 없기 때문에 -1을 리턴합니다.

int attribName = startTag.indexOf("name="); 코드의 의미는 startTag 변수에서 "name=" 이라는 문자열을 찾아서 없으면 -1을, 있으면 문자열의 첫번째 글자인 "n"이 몇번째에 있는지 정수값을 attribName 변수에 저장하라 입니다.

void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      Serial.println("City : ");
    }
  }
}

attribName 변수에 값을 저장했다면 IF문을 통해 -1 이 아닌지 검사합니다. 아니라면 참이므로 City : 문자열을 출력합니다.

이제 name 어트리뷰트를 찾았으니 도시명을 추출하는 코드를 작성해야 합니다.

void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      String cityName = startTag.substring(attribName + 6);
      Serial.println("City : " + cityName);
    }
  }
}

"name="의 인덱스값을 저장한 후에, 우선 -1이 아닌지 즉, 해당 문자열이 존재하는지 체크하고 존재한다면 추출해서 출력까지 완료합니다. 추출하는 방법은 여러가지이겠지만, 저는 다음과 같이 구성하였습니다.

추출하기 위해선 여기서 원하는 값인 "Seoul-teukbyeolsi" 가 startTag 내에서 정확히 몇 번째 위치에서 몇 번째까지 해당하는 지 알아내야 합니다. 그리고, String.substring() 함수를 이용해서 잘라내면 됩니다.

.indexOf("name=") 함수는 찾으려는 문자열이 존재한다면 그 문자열의 첫 번째 글자 'n'의 위치값 즉 인덱스를 리턴합니다. 만약 'n'이 0부터 시작해서 19번째 자리에 있다고 가정해 보겠습니다.

그러면 서울특별시의 'S'는 몇 번째에 위치하고 있을까요? 한 글자씩 세어 보면 'n' 에서 6번째에 위치하고 있습니다. 즉, 25번째에 위치합니다.

int attribName = startTag.indexOf("name=");  // 'n'이 19번째 있으므로 attribName의 값은 19.
// 그리고, attribName + 6 의 값은 25이며 바로 'S'의 위치값

.indexOf(); 함수에 의해서 attribName에 'n'의 위치값이 저장됩니다. 19 이든 아니든 상관없습니다. 그리고 attribName에 6을 더하면 원하는 도시명의 첫번째 글자가 위치하는 인덱스값을 구할 수 있습니다.

그럼, 처음부터 그냥 19 번째 숫자를 이용해서 잘라내면 되지 않을까? 물론 동일한 결과값을 얻을 수 있겠지만, 조금 위험한 접근입니다. 만약 어트리뷰트의 순서가 바뀌거나 중간에 추가된 어트리뷰트가 있다든가 하면 인덱스값은 쉽게 바뀌게 되니까요! 어차피 어트리뷰트 name이 있는지 찾아야 하고, 찾게 되면 인덱스값까지 구할 수 있으니 바로 이용하면 됩니다.

여기까지 찾으려는 도시명의 첫번째 글자가 몇 번째에 위치하는 지 알았습니다. 이제 도시명의 마지막 글자가 어디인지 알아낼 차례입니다. 열쇠는 큰 따옴표입니다. XML 규약에는 어트리뷰트의 속성값은 따옴표로 묶어 줘야 합니다. 그리고, 찾으려는 도시명도 역시 따옴표로 묶여있습니다. 도시명 바로 다음에는 꼭 따옴표가 나오게 되어 있으니 이 따옴표를 찾으면 도시명의 마지막 글자 위치도 바로 알 수 있겠습니다. 문제는, 태그안에 따옴표가 여러 번 나온다는 것입니다. 이를 해결하기 위해 String.substring() 함수를 사용합니다.

.substring()함수는 문자열 변수에서 인수로 받은 인덱스값을 기준으로 문자열을 잘라내는 함수입니다. 인수는 하나가 올수도 있고 두 개가 올 수도 있습니다. 인수가 하나면 그 인덱스부터 마지막까지 잘라내고, 인수가 두 개라면 첫 번째 인수 부터 두 번째 인수 바로 전까지 문자열을 중간에 잘라내게 됩니다. 여기서 두 번째 인수에 해당하는 문자는 포함되지 않음을 유의해야 합니다.

String example = "abc123";
String aaa = example.substring(0);   // 이건 의미가 없습니다. 첫 번째부터 끝까지이므로... "abc123"
String bbb = example.substring(3);  // 인덱스 3, 즉 4번째 부터 끝까지 이므로... "123"
String ccc = example.substring(2, 5);   // 인덱스 2 부터 4까지... "c12"
String cityName = startTag.substring(attribName + 6);

위 코드의 substring() 함수는 인수가 하나입니다. attribName에는 현재 name의 'n'의 위치가 들어있고, 만약 attribName이 19라면 attribName + 6은 25이므로 아래 코드와 의미가 갈습니다.

String cityName = startTag.substring(25);

인수가 하나이므로, 25번째 글자부터 끝까지 잘라냅니다. 25번째 글자는 도시명의 첫글자 'S'이므로 cityName 에 들어가는 값은 아래와 같습니다.

cityName = Seoul-teukbyeolsi">

substring() 함수에 의해 위와 같이 부분 문자열이 cityName에 저장됩니다. 그리고, 위 문자열에는 큰따옴표가 하나이므로 따옴표의 위치만 찾으면 도시명의 끝부분을 알 수 있습니다.

void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      String cityName = startTag.substring(attribName + 6);
int quote = cityName.indexOf("\"");
      Serial.println("City : " + cityName);
    }
  }
}

quote 변수는 인덱스값 즉 정수를 저장해야 하므로 int로 선언하였습니다. cityName은 현재, 위에서 알 수 있듯이, 따옴표가 하나입니다. 그래서, indexOf() 함수를 이용해서 인덱스값을 알 수 있습니다.

.index() 함수의 인수로 변수가 아니라 문자열 상수를 직접 넣어줄 때는 큰 따옴표로 묶어 줘야 합니다. 즉 .index("......"); 이런 식이죠! 여기서 큰 따옴표는 인수 자체가 아니라 문자열 인수를 전달하기 위함입니다. 그런데, 이번 경우처럼 큰따옴표(") 자체를 찾아야 할 경우에 (""") 이런 식으로 입력하면 에러가 납니다. 꼭 이스케이프 문자 \(역슬래시)를 붙여줘야 합니다. 이스케이프 문자 \(역슬래시)는 바로 다음 글자를 인수로 인식하라는 의미입니다.

int quote = cityName.indexOf("\"");

위 코드는 cityName 변수에서 큰 따옴표를 찾아 그 인덱스값을 quote에 저장합니다.

void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      String cityName = startTag.substring(attribName + 6);
int quote = cityName.indexOf("\"");
cityName = cityName.substring(0, quote);
      Serial.println("City : " + cityName);
    }
  }
}

다시 코드 한 줄이 추가되었습니다. 다시 .substring() 함수를 이용해서 부분 문자열을 취합니다.

cityName = cityName.substring(0, quote);

.substring() 함수의 인수가 둘인 경우, 첫 번째 인수부터 시작해서 두 번째 인수 바로 전까지 잘라냅니다. 두 번째 인수 자체는 포함되지 않기에 여기서는 더 편해졌습니다. quote 변수에는 따옴표가 들어 있고, 따옴표는 잘라낼 필요가 없기 때문입니다. 또, cityName 변수는 도시명 의 첫 번째 글자부터 잘라서 저장했기 때문에 첫 번째 인수는 0이 됩니다. 추출해서 다시 cityName 변수에 결과를 저장하므로 cityName에는 원하는 결과값인 도시명만 들어가게 됩니다. 여기까지 저장하고 실행하면 아래와 같이 결과를 확인할 수 있습니다. 이 부분까지의 전체 소스는 바로 아래 소스를 참고하시기 바랍니다. (늘 마찬가지이지만 개인 API Key는 꼭 변경해서 돌리세요~!)

#include <SPI.h>
#include <WiFi101.h>
char ssid[] = "TURTLE";
char pass[] = "yesican1";
bool tagInside = false;  // 태그 안쪽인지 바깥쪽인지 구별하는 변수
bool flagStartTag = false; // 스타트 태그인지 구별하는 변수
String currentTag = ""; // 현재 태그를 저장하기 위한 변수, 공백으로 초기화함
String currentData = ""; // 태그 사이의 컨텐츠를 저장하는 변수
String startTag = ""; // 현재 elements의 start tag 저장
String endTag = "";   // 현재 elements의 end tag 저장
int status = WL_IDLE_STATUS;
char server[] = "api.openweathermap.org";
WiFiClient client;
void setup() {
  //Configure pins for Adafruit ATWINC1500 Feather
  WiFi.setPins(8,7,4,2); 
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);
  }
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);
    // wait 2 seconds for connection:
    delay(2000);
  }
  Serial.println("Connected to wifi");
  Serial.println("\nStarting connection to server..."); 
  if (client.connect(server, 80)) {
    Serial.println("connected to server"); 
    client.println("GET /data/2.5/weather?id=1835847&APPID=7b18dfe35b4273bbe63654d75573cacf&units=metric&mode=xml HTTP/1.1");
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }
}
void loop() { 
  while (client.available()) {
    char c = client.read();
    if (c == '<') {
      tagInside = true;
    }
    if (tagInside) {
      currentTag += c;
    } else if (flagStartTag) {
      currentData += c;
    } 
    if (c == '>') {
      tagInside = false;
      if (currentTag.startsWith("</")) {
        flagStartTag = false;
        endTag = currentTag;  // store end Tag
        if (startTag.indexOf("country") != -1) {
          if (endTag.indexOf("country") != -1) {
            Serial.print("Country : ");
            Serial.println(currentData);
          }
        }
        currentData = ""; 
      } else {
        flagStartTag = true;                
        startTag = currentTag; // store start Tag
        startTagProcessing();
      } 
      currentTag = "";
    }
  } 
  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting from server.");
    client.stop();
    // do nothing forevermore:
    while (true);
  }
}
void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      String cityName = startTag.substring(attribName + 6);
      int quote = cityName.indexOf("\"");
      cityName = cityName.substring(0, quote);
      Serial.println("City : " + cityName);
    }
  }
}

COORD 태그에서 위도, 경도 추출하기

<coord lon="127" lat="37.58">

이 부분은 city 태그의 name 어트리뷰트와 동일한 방식입니다. 한번 더 연습하는 의미로 빠르게 언급하고 넘어 가도록 하겠습니다.

if (startTag.startsWith("<city")) {
 ....... 
} else if (startTag.startsWith("<coord")) {
 ........
{

else if 문으로 연결했습니다. <city 태그가 아니라면 <coord 태그인지 검사하고 처리합니다.

} else if (startTag.startsWith("<coord")) {
    int attribLon = startTag.indexOf("lon=");
 }

.indexOf() 함수를 이용해서 start tag에서 원하는 어트리뷰트(lon=)가 있는지 검사하고, 없으면 -1, 있으면 인덱스값을 정수로 받아 attribLon에 저장한다.

} else if (startTag.startsWith("<coord")) {
    int attribLon = startTag.indexOf("lon=");
    if (attribLon != -1) {
    }
}

name 어트리뷰트와 구조가 역시 동일합니다. attribLon 어트리뷰트가 -1이 아닌지 즉, 태그내에 해당 어트리뷰트가 존재하는 지 검사합니다.

} else if (startTag.startsWith("<coord")) {
    int attribLon = startTag.indexOf("lon=");
    if (attribLon != -1) {
      String coordLon = startTag.substring(attribLon + 5);
      int quote = coordLon.indexOf("\"");
      coordLon = coordLon.substring(0, quote);
      Serial.println("Longitude : " + coordLon);
    }
  }

city 태그에서, "name=" 문자열은 자체가 다섯 글자입니다. 따라서 따옴표 다음 글자는 여섯 번째이기에 attribName에 +6을 해서 substring() 을 했습니다. coord 태그의 "lon=" 문자열은 자체가 네 글자입니다. 따라서 attribLon + 5를 해서 substring() 을 합니다.

이제 String type 변수 coordLon 에는 어떤 값이 들어 있을까요? .substring() 함수의 인수가 하나이므로 "lon=" 다음 글자부터 끝까지 잘라내서 저장합니다.

127" lat="37.58">

위와 같은 결과가 들어 있을 겁니다. 이제 따옴표를 찾아서 그 앞까지 잘라내면 되는데, 위 결과에는 따옴표가 3개나 있지만, indexOf("\"") 함수는 첫 번째 만나는 따옴표에 대해서 인덱스를 리턴하므로 결과는 동일합니다.

이제 substring() 함수를 이용해서 따옴표 앞까지 잘라냅니다. 인수가 2개이니 주어진 범위로 잘라내는데, 이때 두번째 인수가 가리키는 글자는 포함 안되고 바로 앞 글자까지만 추출되는 것을 위에서 확인했었습니다.

두 번째 attribute인 위도 lat는 반복될 뿐이니 생략하겠습니다.

 } else if (startTag.startsWith("<coord")) {
    int attribLon = startTag.indexOf("lon=");
    if (attribLon != -1) {
      String coordLon = startTag.substring(attribLon + 5);
      int quote = coordLon.indexOf("\"");
      coordLon = coordLon.substring(0, quote);
      Serial.println("Longitude : " + coordLon);
    }
    int attribLat = startTag.indexOf("lat=");
    if (attribLat != -1) {
      String coordLat = startTag.substring(attribLat + 5);
      int quote = coordLat.indexOf("\"");
      coordLat = coordLat.substring(0, quote);
      Serial.println("Latitude : " + coordLat);
   }
}

추가된 소스와 결과 화면입니다.

<SUN> 태그에서 일출, 일몰 시간 추출하기

sun rise, sun set 시간을 추출하는 것도 기본 구조는 같지만, 다음 사항이 약간 다릅니다.

  • 시간 데이터는 자릿수가 정해져 있기 때문에 따옴표를 찾을 필요가 없다.
  • 응답메시지의 시간은 UTC 표준시라서 +9 시간씩 보정 해줘야 한다.

1번은 코딩이 더 쉬워지게 하고, 2번은 어렵게 만듭니다. 우리가 사용하는 시간 데이터는 시, 분, 초 모두 두자리씩 끊어 읽으면 되기 때문에 시작 지점만 알면 끝지점은 따로 찾을 필요가 없게 됩니다. 시작 지점은 이제 까지 해왔던 것처럼 .indexOf("rise="), .indexOf("set=")으로 찾으면 되구요! 시와 분은 시작지점에서 글자 수 세어서 잘라내면 됩니다. 초 단위는 생략하겠습니다.

시간 보정은 +9시간씩 해야 하는데, 바로 위에서 잘라낸 시간 데이터를 바로 사용할 수 없습니다. 데이터 형식이 String 형이기 때문인데, 계산할 때는 임시적으로 정수형(Int)으로 변환해서 계산하고 다시 String형으로 변환하는 절차를 넣어야 합니다. 물론, 보정 후에 정수형 데이터를 바로 출력해도 되겠죠.

그리고, 일출 시간을 보정하기 위해 +9시간 하게되면 값이 24를 넘어 버립니다. 날짜는 필요 없으니 다시 24만 빼주면 되는데, +9 - 24 = -15 라서 그냥 15시간씩 감하도록 계산했습니다.

} else if (startTag.startsWith("<sun")) {
    int attribTime = startTag.indexOf("rise=");
    if (attribTime != -1) {
      String riseHour = startTag.substring(attribTime + 17, attribTime + 19); 두자리씩 끊어 읽기
      String riseMin = startTag.substring(attribTime + 20, attribTime + 22);
      int tempValue = riseHour.toInt(); // 계산을 위해 정수형으로 변환
      tempValue -= 15; // +9시간, -24시간 => -15시간
      riseHour = String(tempValue);
      Serial.println("Sun Rise : " + riseHour + ":" + riseMin);
    }
    attribTime = startTag.indexOf("set="); // attribTime변수는 위에서 선언한
    if (attribTime != -1) {
      String setHour = startTag.substring(attribTime + 16, attribTime + 18);
      String setMin = startTag.substring(attribTime + 19, attribTime + 21);
      int tempValue = setHour.toInt();
      tempValue += 9;
      setHour = String(tempValue);
      Serial.println("Sun Set : " + setHour + ":" + setMin);
    }
  }

추가되는 소스와 결과 화면입니다.

위 결과화면은 기온, 습도, 기압까지 추출해서 출력한 모습입니다. 여기 까지 전체 소스는 아래 접혀진 부분을 참고 하시기 바랍니다. 나머지 부분은 반복되는 부분이라서 생략하고, 또 날씨 관련 아이콘 이미지를 제공하는데 시리얼 모니터로 확인할 수 없는 부분이라서 나중에 기회가 되면 다루어 보도록 하겠습니다.

여기까지 client 로 접속해서 데이터를 요청하고, 응답 받은 XML 데이터를 파싱하고 처리하는 부분까지 알아 보았습니다. 다음 글 부터 간단한 Server 구현에 대해 다루도록 하겠습니다. 이상입니다.

#include <SPI.h>
#include <WiFi101.h>
char ssid[] = "TURTLE";
char pass[] = "yesican1";
bool tagInside = false;  // 태그 안쪽인지 바깥쪽인지 구별하는 변수
bool flagStartTag = false; // 스타트 태그인지 구별하는 변수
String currentTag = ""; // 현재 태그를 저장하기 위한 변수, 공백으로 초기화함
String currentData = ""; // 태그 사이의 컨텐츠를 저장하는 변수
String startTag = ""; // 현재 elements의 start tag 저장
String endTag = "";   // 현재 elements의 end tag 저장
int status = WL_IDLE_STATUS;
char server[] = "api.openweathermap.org";
WiFiClient client;
void setup() {
  //Configure pins for Adafruit ATWINC1500 Feather
  WiFi.setPins(8,7,4,2); 
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);
  }
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);
    // wait 2 seconds for connection:
    delay(2000);
  }
  Serial.println("Connected to wifi");
  Serial.println("\nStarting connection to server..."); 
  if (client.connect(server, 80)) {
    Serial.println("connected to server"); 
    client.println("GET /data/2.5/weather?id=1835847&APPID=7b18dfe35b4273bbe63654d75573cacf&units=metric&mode=xml HTTP/1.1");
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }
}
void loop() { 
  while (client.available()) {
    char c = client.read();
    if (c == '<') {
      tagInside = true;
    }
    if (tagInside) {
      currentTag += c;
    } else if (flagStartTag) {
      currentData += c;
    } 
    if (c == '>') {
      tagInside = false;
      if (currentTag.startsWith("</")) {
        flagStartTag = false;
        endTag = currentTag;  // store end Tag
        if (startTag.indexOf("country") != -1) {
          if (endTag.indexOf("country") != -1) {
            Serial.print("Country : ");
            Serial.println(currentData);
          }
        }
        currentData = ""; 
      } else {
        flagStartTag = true;                
        startTag = currentTag; // store start Tag
        startTagProcessing();
      } 
      currentTag = "";
    }
  } 
  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting from server.");
    client.stop();
    // do nothing forevermore:
    while (true);
  }
}
void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      String cityName = startTag.substring(attribName + 6);
      int quote = cityName.indexOf("\"");
      cityName = cityName.substring(0, quote);
      Serial.println("City : " + cityName);
    }
  } else if (startTag.startsWith("<coord")) {
    int attribLon = startTag.indexOf("lon=");
    if (attribLon != -1) {
      String coordLon = startTag.substring(attribLon + 5);
      int quote = coordLon.indexOf("\"");
      coordLon = coordLon.substring(0, quote);
      Serial.println("Longitude : " + coordLon);
    }
    int attribLat = startTag.indexOf("lat=");
    if (attribLat != -1) {
      String coordLat = startTag.substring(attribLat + 5);
      int quote = coordLat.indexOf("\"");
      coordLat = coordLat.substring(0, quote);
      Serial.println("Latitude : " + coordLat);
    }
  } else if (startTag.startsWith("<sun")) {
    int attribTime = startTag.indexOf("rise=");
    if (attribTime != -1) {
      String riseHour = startTag.substring(attribTime + 17, attribTime + 19);
      String riseMin = startTag.substring(attribTime + 20, attribTime + 22);
      int tempValue = riseHour.toInt();
      tempValue -= 15;
      riseHour = String(tempValue);
      Serial.println("Sun Rise : " + riseHour + ":" + riseMin);
    }
    attribTime = startTag.indexOf("set=");
    if (attribTime != -1) {
      String setHour = startTag.substring(attribTime + 16, attribTime + 18);
      String setMin = startTag.substring(attribTime + 19, attribTime + 21);
      int tempValue = setHour.toInt();
      tempValue += 9;
      setHour = String(tempValue);
      Serial.println("Sun Set : " + setHour + ":" + setMin);
    }
  } else if (startTag.startsWith("<temperature")) {
    int attribValue = startTag.indexOf("value=");
    if (attribValue != -1) {
      String tempValue = startTag.substring(attribValue + 7);
      int quote = tempValue.indexOf("\"");
      tempValue = tempValue.substring(0, quote);
      Serial.println("Temperature : " + tempValue);
    }
  } else if (startTag.startsWith("<humidity")) {
    int attribValue = startTag.indexOf("value=");
    if (attribValue != -1) {
      String humidValue = startTag.substring(attribValue + 7);
      int quote = humidValue.indexOf("\"");
      humidValue = humidValue.substring(0, quote);
      Serial.println("Humidity : " + humidValue + "%");
    }
  }else if (startTag.startsWith("<pressure")) { 
    int attribValue = startTag.indexOf("value=");
    if (attribValue != -1) {
      String pressValue = startTag.substring(attribValue + 7);
      int quote = pressValue.indexOf("\"");
      pressValue = pressValue.substring(0, quote);
      Serial.println("Pressure : " + pressValue + " hPa");
    }
  }
}


Comments