WiFi를 통한 아두이노 활용(13) : 날씨 정보 제공 #3

2017. 8. 4. 16:47

Arduino/Wireless

OpenWeatherMap로부터 선택한 도시의 날씨 정보를 받아 웹페이지로 제공

이전 글에 이어서 openweathermap.org에서 전송 받은 날씨 정보를 웹페이지로 제공하는 간단한 웹서버를 완성하도록 하겠습니다.

이번 글에서 처리해야 할 사항은 아래와 같습니다.

  • 웹페이지를 통해 클라이언트로부터 입력 받은 데이터를 분석하는 기능
  • 분석한 데이터를 적용하여 변경된 날씨 정보를 요청하는 기능
  • 변경된 날씨 정보에 따라 웹페이지를 수정하여 제공하는 기능

변수와 상수

위 세 가지 사항을 처리하기 위해 가장 먼저 준비해야 할 부분이 바로 몇몇 상수를 변수로 변경하는 일입니다. 변수와 상수 모두 프로그램상에서 필요한 데이터를 나타냅니다. 변수는 이름 그대로 내용이 변할 수 있는 저장소의 의미입니다. 원하는 내용을 담아서 원하는 시점에 사용합니다.

상수는 이름 그대로 항상 동일한 값을 가지고 있습니다. 데이터 그 자체일 수도 있고, 변수처럼 저장소에 담길 수도 있으며, 이해하기 쉽게 하기 위해 별명을 붙여 사용할 수도 있지만 결국 의미하는 값은 변하지 않습니다.

openweathermap.org에 날씨 정보를 요청할 때, 이제 까지 "서울"의 데이터를 붙러오는 예제를 다루었습니다. openweathermap API에는 각 도시마다 고유한 ID값을 부여했기 때문에, 이 ID값을 이용하면 원하는 도시를 선택할 수 있습니다.

if (client.connect(server, 80)) {
    Serial.println("connected to server");
    //
    client.println("GET /data/2.5/weather?id=1835847&APPID=개인키&units=metric&mode=xml HTTP/1.1");
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
}

이제 까지 사용했던 Request Message입니다. 네 번째 줄(GET .....)의 "id=1835847"이 바로 서울의 City ID값을 제공하는 부분이며 ID값 자체 1835847이 그대로 삽입되어 있습니다. 즉 상수입니다. 다른 도시로 변경하고 싶다면 매번 소스 코드를 변경하거나 변경 가능하도록 변수로 바꿔야 합니다. 더불어, APP ID는 값이 변하진 않지만 너무 길어서 변수로 바꾸어 사용하겠습니다. 표현 단위(units)나 전송 포맷(mode)도 변경하여 사용하고 싶다면 변수로 바꿔야 하겠죠.

#include <SPI.h>
#include <WiFi101.h>
//
char ssid[] = "TURTLE";
char pass[] = "yesican1";
//
String cityID = "1835847"; // 기본값은 서울
String appID = "7b18dfe35b4273bbe63654d75573cacf"; // 각 계정의 APP ID

위와 같이 프로그램 상단에 두 개의 String type 변수를 선언하고 초기값을 주었습니다. cityID값을 변경하지 않는다면 항상 서울을 요청하도록 기본값을 주었습니다. (위 App ID는 가짜입니다.^^)

void OpenWeatherMap() {
//
  Serial.println("\nStarting connection to server...");
  //
  if (client.connect(server, 80)) {
    Serial.println("connected to server");
    //
    client.println("GET /data/2.5/weather?id=" + cityID + "&APPID=" + appID + "&units=metric&mode=xml HTTP/1.1");
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }

그리고, 클라이언트 요청 메시지도 위와 같이 상수값을 해당 변수로 치환하였습니다. 상수를 변수로 치환했을 뿐 의미하는 값은 동일하기 때문에 결과도 동일하게 볼 수 있습니다.

GET 요청문이 변수와 상수가 섞이는 바람에 더 복잡해졌습니다. .println(); 함수내에서 상수는 꼭 따옴표로 묶어야 하고 변수는 그러면 안되기 때문에 위와 같은 모양이 되었습니다. "+"는 문자열 연결 연산자입니다.

if (client.connect(server, 80)) {
    Serial.println("connected to server");
    String request = "GET /data/2.5/weather?id=";
    request += cityID;
    request += "&APPID=";
    request += appID;
    request += "&units=metric&mode=xml HTTP/1.1"; 
    client.println(request);
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }

마지막으로, 요청 메시지를 조합하는 과정이 좀 더 보기 편하게 하기 위해 repuest라는 변수를 하나 선언하고 요청 메시지를 저장할 수 있도록 수정했습니다.

웹 클라이언트 요청 메시지 처리

출력만 하던 웹서버를 이제 입력도 받도록 변경하겠습니다. 웹서버의 입장에서 웹 클라이언트의 Request Message를 처리해야 합니다. 그런데, 바로 위에 있는 코드는 그 반대로 웹 클라이언트의 입장에서 웹서버(openweathermap.org)에 Request Message를 보내는 코드입니다. 따라서 웹서버로서 처리해야할 부분이 어디인지 이미 알고 있습니다. 바로 "GET ~~~" 구문입니다.

GET /?cityname=1838519 HTTP/1.1
Host: 192.168.1.65
Connection: keep-alive
.....

이전 글에서 확인했던 부분입니다. 프로그램을 실행하고 웹페이지에서 "부산광역시"를 선택한 후의 요청문인데, 지금은 첫 번째 줄만 처리할 수 있으면 됩니다.

역시, 예전 글에서 봤던 부분으로, 요청 메시지는 문장마다 제어문자가 숨어 있습니다. '\r'은 CR, Carrage Return으로 문장 처음으로 커서를 옮기고, '\n'은 LF, Line Feed로 커서를 다음 줄로 옮깁니다. 그래서, '\r\n'은 줄바꿈의 기능을 수행하게 됩니다. 마지막 줄은 빈 줄로서 메시지의 끝을 알려줍니다.

요청 메시지의 모든 문자는 세 가지로 분류할 수 있습니다. CR, LF, 그외 모든 문자. CR(\r)은 무시하기로 했으니 처리하는 부분은 필요 없고 체크하는 곳만 있으면 됩니다. LF(\n)는 또 두 가지로 분류됩니다. 각 문장 끝에 오는 것과 연속으로 두 개가 오는 메시지 끝입니다. 이에 따라서 요청 메시지 처리 부분을 아래와 같이 작성했었습니다.

이미 예전 글에서 다루었던 부분이지만 복잡할 수 있으니 다시 한번 짚어 보겠습니다.

while (Webclient.connected()) {
    if (Webclient.available()) {
        char c = Webclient.read();
        Serial.write(c);
        if (c == '\n') { // LF가 들어올 때
            if (newLine) {  // LF가 연속으로 들어왔다면 메시지 끝
              // 메시지 끝이므로 웹페이지로 응답
            } else {  // 메시지 끝이 아닌 문장 끝
              // 한 문장이 끝났을 때 필요한 처리
            }
        } else if (c != '\r') {  // 나머지 CR이 아닌 모든 문자가 여기 해당
            // 읽어온 한 문자씩 변수에 저장
        }
    }
}

클라이언트로 부터 온 요청 메시지를 처리하는 부분은 위와 같이 간단히 할 수 있습니다. 그리고, 이를 그림으로 표현하면 아래와 같습니다.

그림이 오히려 복잡해 지도록 하는 건 아닌지...!^^; 아무튼, 위와 같은 구조임을 염두에 두고 코딩을 추가해 보겠습니다.

void loop() {
  WiFiClient Webclient = Webserver.available();
  if (Webclient) {
    Serial.println("new client");
    bool newLine = false;
    String currentLine = ""; // 전송 받은 문장을 저장할 변수
    while (Webclient.connected()) {

우선, 위와 같이 전송 받은 메시지를 저장할 변수를 선언해 줍니다. 이전까지는 저장하지 않았습니다. 이제, 한 문자씩 받을 때마다 CR, LF가 아니라면 여기에 저장할 것이고, 한 문장이 완성된 후엔 다음 문장을 받기 위해 초기화를 해줘야 합니다.

while (Webclient.connected()) {
    if (Webclient.available()) {
        char c = Webclient.read();
        Serial.write(c);
        if (c == '\n') { 
            if (newLine) { 
            } else { 
                newLine = true;
            }
        } else if (c != '\r') {  
            currentLine += c; // 한 문자씩 저장하는 부분
            newLine = false;
        }
    }
}

위와 같이 선언한 변수에 문자를 저장하는 코드를 적당한 위치에 넣습니다. 저 위치까지 왔다면 LF가 아니라는 의미이며, CR이 아니라면 if문 안쪽으로 들어와 문자를 저장하게 됩니다.

while (Webclient.connected()) {
    if (Webclient.available()) {
        char c = Webclient.read();
        Serial.write(c);
        if (c == '\n') { 
            if (newLine) { 
            } else { 
                newLine = true; // 한 문장이 끝나고 새 문장이 시작될 시점 체크
                if (currentLine.indexOf("cityname") != -1) { // 문장중에 cityname 단어가 있다면...
                    requestProcess(currentLine);  // 문장 처리 함수 call
                }
                currentLine = ""; // 문장처리 후, 새 문장 받을 준비 즉 초기화
            }
        } else if (c != '\r') {  
            currentLine += c; // 한 문자씩 저장하는 부분
            newLine = false;
        }
    }
}

LF('\n')가 들어 왔을 때, newLine 변수값이 true라면 한 문장이 끝나고 새 문장이 시작할 시점에 줄바꿈 기호가 또 들어온 것이므로 메시지 끝을 의미하는 빈 줄에 해당하고, newLine이 false라면 그냥 한 문장이 끝났다는 의미이므로 완성된 문장에 대한 처리를 합니다. 여기서는 requestProcess() 라는 별도의 함수를 만들어 호출하였습니다. 인수로는 현재 문장이 들어있는 currentLine을 넘겨 줍니다. 마지막으로 문장 처리가 완료된 후, 새 문장을 받기 위해 currentLine 변수를 초기화 해줍니다.

requestProcess() 함수를 호출하는 부분은 IF문으로 처리하고 있습니다. IF문으로 원하는 문장만 처리하고자 함인데, 문장중에 "cityname"이라는 단어가 있어야 합니다. 그래서, IF문의 조건에 cityname이 존재하는지 알기 위해 String.indexOf("cityname")함수를 이용했습니다. .indexOf() 함수는 인수로 주어진 문자열이 해당 String에 존재하면 그 인덱스값(몇 번째)을 리턴하고 찾을 수 없으면 -1값을 리턴합니다. 따라서, cityname이 존재할 때만 처리 함수 requestProcess()를 호출할 수 있습니다.

하지만, 이렇게 cityname을 찾는 조건으로 사용하면 제대로 동작하지 않습니다. 첫 번째로 도시를 선택했을 때는 잘 적용되는데, 두 번째 부터는 꼭 이전에 선택한 도시명이 적용됩니다. 우선, 위와 같은 코드로 처리한 후 나중에 이 부분에 대해서 언급하겠습니다.

void requestProcess(String requestLine) {
  int citynameIndex = requestLine.indexOf("cityname");
  if (citynameIndex != -1) {  // cityname이 존재한다면...
    cityID = requestLine.substring(citynameIndex + 9, citynameIndex + 16);
  }
  Serial.println("City ID : " + cityID);
}

문장 처리 함수입니다. 요청 메시지로 들어오는 값이 단 한가지로 정해져 있기 때문에 코드가 간결합니다. 만약, 값이 여러 개라면 이 함수 안쪽에서 IF문이 나열될 것입니다.

String.indexOf() 함수로 cityname의 위치를 알아 내고, String.substring()함수를 이용해 원하는 문자열만 잘라내었습니다. 원하는 문자열인 도시의 ID값은, indexOf("cityname")으로 찾은 첫 번째 글자 'c'의 위치에서 9번째에 있고, 그 위치에서 7글자를 잘라내면 됩니다. indexOf()substring() 함수는 예전 글에서 다루었으므로 참고하기 바랍니다. 잘라내려는 도시 ID값이 7자리로 정해져 있기 때문에 좀더 편하게 구현했습니다. 만약 자릿수가 정해지지 않은 요청값을 처리하는 경우가 있다면 문장 끝에 항상 오는 "HTTP/1.1" 문구의 위치를 알아내서 이용하면 됩니다.

여기까지 코딩을 완료하고 실행하면 원하는 대로 선택한 도시의 날씨 정보를 처음 한 번만 볼 수 있습니다.

위 캡처 화면 처럼 부산을 선택한 후에 잘 적용되어서 출력하고 있습니다. City Name이 Pusan으로 시작하네요! 하지만, 여기서 다른 도시, 예를 들어 대구를 선택하면 다시 부산이 출력됩니다. 또 인천을 선택하면 대구가 출력됩니다. 이렇게 출력되는 이유를 설명하겠습니다.

처음 웹서버에 접속할 때, http://192.168.1.65 주소를 치고 접속했습니다. 

위쪽 그림과 같이 주소표시줄을 보면 확인할 수 있습니다. 그 다음, 부산을 선택하면...

아래쪽 그림처럼 주소가 길어졌습니다. 이는 여기서 사용한 메소드가 GET방식이기 때문입니다. GET방식은 요청하는 정보를 주소에 담아서 보내기에 위처럼 현재 주소가 변경되어 있습니다.

Referer: http://192.168.1.65/

웹페이지가 출력된 후, 첫 번째로 도시를 선택해서 전송하면 클라이언트에서 전송된 Request Message중에 위와 같은 문장이 있습니다. Referer는 웹페이지에서 링크를 눌러 이동했을 때 링크가 있던 이전 주소를 같이 보내는 기능입니다. 이 기능을 통해 어디에서 유입됐는지 대충 파악할 수 있는데, 문제는 두 번째로 도시를 선택했을 때입니다.

Referer: http://192.168.1.65/?cityname=1838519

GET메소드에 의해 현재 주소가 변경되었을 경우, Referer도 역시 같은 주소를 보냅니다. 위 문장은 처음에 부산을 선택한 후에 또 다시 대구를 선택했을 때 전송된 Referer입니다. 그리고, 저 ID는 부산을 의미하구요! 이동하려는 주소가 아니라 링크를 누른 주소이기 때문에 항상 이전에 선택한 도시 아이디가 Referer에 담겨 있습니다.

이 Referer문에도 cityname이라는 단어가 있기 때문에 메시지를 분석하는 소스 코드가 이전 도시로 보내 버립니다. 이를 해결하기 위해선, cityname 같은 요청값이 아닌 부분에서 항상 들어있는 단어를 골라주면 되는데, 그래서, "HTTP/1.1"을 사용하여 이를 해결하였습니다.

if (currentLine.indexOf("HTTP/1.1") != -1) {
    requestProcess(currentLine);
}

위와 같이 변경하면 정상적으로 작동합니다.

폼 엘리먼트 변경하기 : Drop-Down List 디폴트값 변경

결과는 정상적으로 출력되지만 눈에 거슬리는 곳이 한 곳 입니다. 위 그림에서 보면, 부산을 선택하든 울산을 선택하든 드롭다운리스트는 항상 첫 번째 리스트 항목인 서울을 보여줍니다. 이를 선택한 도시에 따라 변경하여 출력하도록 코드를 수정하겠습니다.

<option value="1835847" selected>부산광역시</option>

드롭다운리스트의 리스트 항목을 생성하는 폼 요소인 <option>태그에 selected 어트리뷰트를 사용하면 디폴트로 선택될 항목을 지정할 수 있습니다.

String cityID = "1835847"; // 기본값은 서울
String appID = "7b18dfe35b4273bbe63654d75573cacf"; // 각 계정의 APP ID
String selected1 = "";
String selected2 = "";
String selected3 = "";
String selected4 = "";
String selected5 = "";
String selected6 = "";
String selected7 = "";

요청한 값에 따라 selected attribute를 저장할 변수를 도시 수만큼 7개 생성했습니다.

Webclient.println("<option value=\"1835847\" " + selected1 + ">서울특별시</option>");
Webclient.println("<option value=\"1838519\" " + selected2 + ">부산광역시</option>");
Webclient.println("<option value=\"1835327\" " + selected3 + ">대구광역시</option>");
Webclient.println("<option value=\"1843561\" " + selected4 + ">인천광역시</option>");
Webclient.println("<option value=\"1841808\" " + selected5 + ">광주광역시</option>");
Webclient.println("<option value=\"1835224\" " + selected6 + ">대전광역시</option>");
Webclient.println("<option value=\"1833742\" " + selected7 + ">울산광역시</option>");

그리고, 웹문서를 출력하는 부분을 변수를 포함하도록 수정했습니다.

void requestProcess(String requestLine) {
  int citynameIndex = requestLine.indexOf("cityname");
  if (citynameIndex != -1) {
    cityID = requestLine.substring(citynameIndex + 9, citynameIndex + 16);
  }
  selected1 = "";  // 선택한 하나만 값을 가지도록
  selected2 = "";  // 우선 모두 공백으로 초기화 함
  selected3 = "";
  selected4 = "";
  selected5 = "";
  selected6 = "";
  selected7 = "";
  if (cityID == "1835847") {
    selected1 = "selected";
  } else if (cityID == "1838519") {
    selected2 = "selected";
  } else if (cityID == "1835327") {
    selected3 = "selected";
  } else if (cityID == "1843561") {
    selected4 = "selected";
  } else if (cityID == "1841808") {
    selected5 = "selected";
  } else if (cityID == "1835224") {
    selected6 = "selected";
  } else if (cityID == "1833742") {
    selected7 = "selected";
  }
}

요청문을 처리하는 함수를 위와 같이 수정했습니다. 우선 7개의 변수를 모두 공백으로 초기화 합니다. 7개중 하나만 "selected"값을 가지고 있도록 하기 위함이며, if ~ else 구문을 이용하여 city ID값에 따라 해당 변수에만 값을 저장하도록 구성했습니다. 수정 후 실행해보면 드롭다운 리스의 기본 항목이 날씨 정보의 도시값을 따라 갑니다.

배열을 사용한다면 좀더 깔끔하게 작성할 수도 있습니다. 리스트의 항목 수가 많을수록 배열로 작성하는 게 유리하다고 봅니다.

여기까지 전체 소스

#include <SPI.h>
#include <WiFi101.h>
char ssid[] = "TURTLE";
char pass[] = "yesican1";
String cityID = "1835847"; // 기본값은 서울
String appID = "7b08dfe35b4273bbe63604c75573cacf"; // 각 계정의 APP ID
String selected1 = "";
String selected2 = "";
String selected3 = "";
String selected4 = "";
String selected5 = "";
String selected6 = "";
String selected7 = "";
bool tagInside = false;  // 태그 안쪽인지 바깥쪽인지 구별하는 변수
bool flagStartTag = false; // 스타트 태그인지 구별하는 변수
String currentTag = ""; // 현재 태그를 저장하기 위한 변수, 공백으로 초기화함
String currentData = ""; // 태그 사이의 컨텐츠를 저장하는 변수
String startTag = ""; // 현재 elements의 start tag 저장
String endTag = "";   // 현재 elements의 end tag 저장
String country = "";
String cityName = "";
String coordLon = "";
String coordLat = "";
String sunRise = "";
String sunSet = "";
String tempValue = "";
String humidValue = "";
String pressValue = "";
int status = WL_IDLE_STATUS;
char server[] = "api.openweathermap.org";
IPAddress ip;
WiFiServer Webserver(80); // Server 서비스를 위한 클래스 인스턴스
WiFiClient client;
void setup() {
  WiFi.setPins(8,7,4,2);
  Serial.begin(9600);
  delay(1000);
  // 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");
  OpenWeatherMap();
  Webserver.begin();
  ip = WiFi.localIP();
  Serial.print("To see this page in action, open a browser to http://");
  Serial.println(ip);
}
void loop() {
  WiFiClient Webclient = Webserver.available();
  if (Webclient) {
    Serial.println("new client");
    bool newLine = false;
    String currentLine = "";
    while (Webclient.connected()) {
      if (Webclient.available()) {
        char c = Webclient.read();
        Serial.write(c);
        if (c == '\n') {
          if (newLine) {
            Serial.println("Client Request Ended!");
            Serial.println("Weather Data Request!");
            OpenWeatherMap();
            Serial.println("Web page transfer Start!");
            Webclient.println("HTTP/1.1 200 OK");
            Webclient.println("Content-type:text/html");
            Webclient.println("Connection: close");
            Webclient.println();
            Webclient.println("<!DOCTYPE html>");
            Webclient.println("<html>");
            Webclient.println("<head>");
            Webclient.println("<meta charset=\"UTF-8\">");
            Webclient.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
            Webclient.println("<title>openweathermap Weather Info</title>");
            Webclient.println("</head>");
            Webclient.println("<body>");
            Webclient.println("<h1>Weather Info of City</h1>");
            Webclient.println("<form>");
            Webclient.println("<select name=\"cityname\">");
            Webclient.println("<option value=\"1835847\" " + selected1 + ">서울특별시</option>");
            Webclient.println("<option value=\"1838519\" " + selected2 + ">부산광역시</option>");
            Webclient.println("<option value=\"1835327\" " + selected3 + ">대구광역시</option>");
            Webclient.println("<option value=\"1843561\" " + selected4 + ">인천광역시</option>");
            Webclient.println("<option value=\"1841808\" " + selected5 + ">광주광역시</option>");
            Webclient.println("<option value=\"1835224\" " + selected6 + ">대전광역시</option>");
            Webclient.println("<option value=\"1833742\" " + selected7 + ">울산광역시</option>");
            Webclient.println("</select>");
            Webclient.println("<input type=\"submit\">");
            Webclient.println("</form>");
            Webclient.println("<p>Country : " + country + "</p>");
            Webclient.println("<p>City Name : " + cityName + "</p>");
            Webclient.println("<p>Longitude : " + coordLon + "</p>");
            Webclient.println("<p>Latitude : " + coordLat + "</p>");
            Webclient.println("<p>Sun Rise : " + sunRise + "</p>");
            Webclient.println("<p>Sun Set : " + sunSet + "</p>");
            Webclient.println("<p>Temperature : " + tempValue + "</p>");
            Webclient.println("<p>Humidity : " + humidValue + "</p>");
            Webclient.println("<p>Pressure : " + pressValue + "</p>");
            Webclient.println("</body>");
            Webclient.println("</html>");
            break;
          } else {
            newLine = true;
            if (currentLine.indexOf("HTTP/1.1") != -1) {
              requestProcess(currentLine);
            }
            currentLine = "";
          }
        } else if (c != '\r') {
          currentLine += c;
          newLine = false;
        }
      }
    }
    Webclient.stop();
    Serial.println("client disonnected");
  }
}
void requestProcess(String requestLine) {
  int citynameIndex = requestLine.indexOf("cityname");
  cityID = requestLine.substring(citynameIndex + 9, citynameIndex + 16);
  selected1 = "";
  selected2 = "";
  selected3 = "";
  selected4 = "";
  selected5 = "";
  selected6 = "";
  selected7 = "";
  if (cityID == "1835847") {
    selected1 = "selected";
  } else if (cityID == "1838519") {
    selected2 = "selected";
  } else if (cityID == "1835327") {
    selected3 = "selected";
  } else if (cityID == "1843561") {
    selected4 = "selected";
  } else if (cityID == "1841808") {
    selected5 = "selected";
  } else if (cityID == "1835224") {
    selected6 = "selected";
  } else if (cityID == "1833742") {
    selected7 = "selected";
  }
}
void OpenWeatherMap() {
  Serial.println("\nStarting connection to server...");
  if (client.connect(server, 80)) {
    Serial.println("connected to server");
    String request = "GET /data/2.5/weather?id=";
    request += cityID;
    request += "&APPID=";
    request += appID;
    request += "&units=metric&mode=xml HTTP/1.1";
    client.println(request);
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }
  while (client.connected()) {
    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;
          if (startTag.indexOf("country") != -1) {
            if (endTag.indexOf("country") != -1) {
              Serial.print("Country : ");
              Serial.println(currentData);
              country = currentData;
            }
          }
          currentData = "";
        } else {
          flagStartTag = true;
          startTag = currentTag;
          startTagProcessing();
        }
        currentTag = "";
      }
    }
  }
  Serial.println();
  Serial.println("disconnecting from server.");
  client.stop();
}
void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      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) {
      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) {
      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);
      sunRise = 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);
      sunSet = setHour + ":" + setMin;
    }
  } else if (startTag.startsWith("<temperature")) {
    int attribValue = startTag.indexOf("value=");
    if (attribValue != -1) {
      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) {
      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) {
      pressValue = startTag.substring(attribValue + 7);
      int quote = pressValue.indexOf("\"");
      pressValue = pressValue.substring(0, quote);
      Serial.println("Pressure : " + pressValue + " hPa");
    }
  }
}

배열을 사용하는 코드는 아래 소스를 참고하세요!!

#include <SPI.h>
#include <WiFi101.h>
char ssid[] = "TURTLE";
char pass[] = "yesican1";
String cityID = "1835847"; // 기본값은 서울
String appID = "7b08dfe35b4273bbe63604c75573cacf"; // 각 계정의 APP ID
String optionTag[7][3] = { {"1835847", "", "서울특별시"},
                           {"1838519", "", "부산광역시"},
                           {"1835327", "", "대구광역시"},
                           {"1843561", "", "인천광역시"},
                           {"1841808", "", "광주광역시"},
                           {"1835224", "", "대전광역시"},
                           {"1833742", "", "울산광역시"} };
bool tagInside = false;  // 태그 안쪽인지 바깥쪽인지 구별하는 변수
bool flagStartTag = false; // 스타트 태그인지 구별하는 변수
String currentTag = ""; // 현재 태그를 저장하기 위한 변수, 공백으로 초기화함
String currentData = ""; // 태그 사이의 컨텐츠를 저장하는 변수
String startTag = ""; // 현재 elements의 start tag 저장
String endTag = "";   // 현재 elements의 end tag 저장
String country = "";
String cityName = "";
String coordLon = "";
String coordLat = "";
String sunRise = "";
String sunSet = "";
String tempValue = "";
String humidValue = "";
String pressValue = "";
int status = WL_IDLE_STATUS;
char server[] = "api.openweathermap.org";
IPAddress ip;
WiFiServer Webserver(80); // Server 서비스를 위한 클래스 인스턴스
WiFiClient client;
void setup() {
  WiFi.setPins(8,7,4,2);
  Serial.begin(9600);
  delay(1000);
  // 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");
  OpenWeatherMap();
  Webserver.begin();
  ip = WiFi.localIP();
  Serial.print("To see this page in action, open a browser to http://");
  Serial.println(ip);
}
void loop() {
  WiFiClient Webclient = Webserver.available();
  if (Webclient) {
    Serial.println("new client");
    bool newLine = false;
    String currentLine = "";
    while (Webclient.connected()) {
      if (Webclient.available()) {
        char c = Webclient.read();
        Serial.write(c);
        if (c == '\n') {
          if (newLine) {
            Serial.println("Client Request Ended!");
            Serial.println("Weather Data Request!");
            OpenWeatherMap();
            Serial.println("Web page transfer Start!");
            Webclient.println("HTTP/1.1 200 OK");
            Webclient.println("Content-type:text/html");
            Webclient.println("Connection: close");
            Webclient.println();
            Webclient.println("<!DOCTYPE html>");
            Webclient.println("<html>");
            Webclient.println("<head>");
            Webclient.println("<meta charset=\"UTF-8\">");
            Webclient.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
            Webclient.println("<title>openweathermap Weather Info</title>");
            Webclient.println("</head>");
            Webclient.println("<body>");
            Webclient.println("<h1>Weather Info of City</h1>");
            Webclient.println("<form>");
            Webclient.println("<select name=\"cityname\">");
            for (int i = 0; i < 7; i++) {
              String option = "<option value=\"";
              option += optionTag[i][0];
              option += optionTag[i][1];
              option += optionTag[i][2];
              option += "</option>";
              Webclient.println(option);
            }
            Webclient.println("</select>");
            Webclient.println("<input type=\"submit\">");
            Webclient.println("</form>");
            Webclient.println("<p>Country : " + country + "</p>");
            Webclient.println("<p>City Name : " + cityName + "</p>");
            Webclient.println("<p>Longitude : " + coordLon + "</p>");
            Webclient.println("<p>Latitude : " + coordLat + "</p>");
            Webclient.println("<p>Sun Rise : " + sunRise + "</p>");
            Webclient.println("<p>Sun Set : " + sunSet + "</p>");
            Webclient.println("<p>Temperature : " + tempValue + "</p>");
            Webclient.println("<p>Humidity : " + humidValue + "</p>");
            Webclient.println("<p>Pressure : " + pressValue + "</p>");
            Webclient.println("</body>");
            Webclient.println("</html>");
            break;
          } else {
            newLine = true;
            if (currentLine.indexOf("HTTP/1.1") != -1) {
              requestProcess(currentLine);
            }
            currentLine = "";
          }
        } else if (c != '\r') {
          currentLine += c;
          newLine = false;
        }
      }
    }
    Webclient.stop();
    Serial.println("client disonnected");
  }
}
void requestProcess(String requestLine) {
  int citynameIndex = requestLine.indexOf("cityname");
  cityID = requestLine.substring(citynameIndex + 9, citynameIndex + 16);
  for (int i = 0; i < 7; i++) {
    if (optionTag[i][0] == cityID)
      optionTag[i][1] = "\" selected>";
    else
      optionTag[i][1] = "\">";
  }
}
void OpenWeatherMap() {
  Serial.println("\nStarting connection to server...");
  if (client.connect(server, 80)) {
    Serial.println("connected to server");
    String request = "GET /data/2.5/weather?id=";
    request += cityID;
    request += "&APPID=";
    request += appID;
    request += "&units=metric&mode=xml HTTP/1.1";
    client.println(request);
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }
  while (client.connected()) {
    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;
          if (startTag.indexOf("country") != -1) {
            if (endTag.indexOf("country") != -1) {
              Serial.print("Country : ");
              Serial.println(currentData);
              country = currentData;
            }
          }
          currentData = "";
        } else {
          flagStartTag = true;
          startTag = currentTag;
          startTagProcessing();
        }
        currentTag = "";
      }
    }
  }
  Serial.println();
  Serial.println("disconnecting from server.");
  client.stop();
}
void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      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) {
      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) {
      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);
      sunRise = 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);
      sunSet = setHour + ":" + setMin;
    }
  } else if (startTag.startsWith("<temperature")) {
    int attribValue = startTag.indexOf("value=");
    if (attribValue != -1) {
      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) {
      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) {
      pressValue = startTag.substring(attribValue + 7);
      int quote = pressValue.indexOf("\"");
      pressValue = pressValue.substring(0, quote);
      Serial.println("Pressure : " + pressValue + " hPa");
    }
  }
}

이상으로 날씨 정보를 출력하는 소스는 마무리하겠습니다.

Comments