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

2017. 8. 3. 08:33

Arduino/Wireless

OpenWeatherMap의 날씨 정보를 받아 웹페이지로 제공하는 프로그램 작성

이전 글에서, 간단한 서버 예제를 통해 웹페이지를 클라이언트에게 전송하는 프로그램을 작성 했습니다. 이번 글부터는 OpenWeatherMap.org에서 날씨 데이터를 받아 웹페이지로 출력하는 소스를 구현하겠습니다. 한 프로그램 안에서 클라이언트 역할과 서버 역할을 동시에 수행하게 됩니다. 그리고, 날씨 데이터를 받아 올 때 서울만 고정해서 연습했었는데, 여러 도시중에 선택해서 받아 오는 부분도 다룰 예정입니다. 코드량이 좀더 많아지긴 하지만, 이미 만들어 놓은 예제 두 개를 합치는 것이므로 대부분의 코드는 복사해서 쓰시면 됩니다.

우선, OpenWeatherMap Client 예제를 불러와서 필요한 수정을 하겠습니다.

참고 : WiFi 를 통한 아두이노 활용(7) : 날씨 정보 #4

이전 예제는 날씨 데이터를 딱 한번만 가져와서 출력합니다. 하지만, 이제 부터 만들려고 하는 서버 예제는 클라이언트 접속이 발생할 때마다 날씨 데이터를 불러와서 출력하기 때문에 변경이 불가피합니다.

void OpenWeatherMap() { }

이를 해결하기 위해서, 위와 같은 함수를 하나 만들고 그 안에 날씨 데이터를 전송받아 처리하는 부분을 몽땅 담을 예정입니다. 이렇게 하면 언제든지, 몇 번이고 필요할 때마다 불러 쓰면 되고, 또 나중에 원하는 도시를 선택하여 전송 받을 때도 적합합니다.

전송 받은 데이터는 추출하여 전역 변수에 저장하기 때문에 별도의 리턴값은 없어서 void형으로 선언했습니다. 인수도 지금은 필요가 없어서 비워 두었습니다.

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

setup() 함수 이전의 선언 부분입니다. 이전 소스를 복사해서 그대로 사용하면 됩니다. 익숙한 두 개의 헤더 파일을 include하고, 무선랜 접속 정보를 미리 지정합니다.

그 다음, XML 데이터의 파싱을 위한 변수들이 선언되어 있습니다.

XML 태그는 "<, >"로 둘러 싸여 있고, 태그 안쪽과 바깥쪽을 구별하여 저장할 필요가 있기 때문에 이를 구분하기 위한 flag 변수 tagInside를 선언했습니다. XML 문서는 일반적으로 시작 태그(start tag, <tag>)와 끝 태그(end tag, </tag>)로 짝을 이루고, 그 사이에 전송하려는 데이터인 컨텐츠가 존재합니다. 이 데이터 컨텐츠를 추출하기 위해선 시작 태그가 끝난 지점을 알아야 하기 때문에 flagStartTag 변수를 선언하여 사용합니다.

서버로부터 전송된 데이터는 문자열 상태로 도착합니다. 한 문자씩 버퍼에 쌓이기 때문에, 하나씩 읽어서 원하는 단어가 완성될 때까지 저장할 임시 변수가 필요한데, currentTag, currentData가 이에 해당합니다.

currentTag에 저장되는 데이터가 하나의 태그가 되었다면 시작 태그인지 끝 태그인지 구별해서 각각 startTag, endTag에 저장합니다.

int status는 WiFi모듈의 상태값을 받는 정수형 변수입니다.

위의 server명은 날씨 데이터를 요청하기 위한 호스트 주소로 OpenWeatherMap.org에서 제공하는 서버입니다. 자세한 정보는 이전 글들을 참고하기 바랍니다.

마지막으로, WiFiClient Class의 인스턴스를 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(); // 날씨 데이터를 요청하고 처리하는 함수 호출
}

setup() 함수의 전체 내용입니다.

우선, 첫 번째 코드는 WiFi 모듈의 SPI 핀을 설정하는 명령입니다. Adafruit Feather M0 보드로 WiFi101 라이브러리를 사용할 때 필요한 부분입니다. 그리고, 모니터링을 위해 Serial 모니터를 시작한 후, 준비를 위해 1초간 딜레이를 주었습니다.

그 다음은, WiFi 쉴드 또는 WiFi 모듈이 잘 연결되었는지 확인 하는 부분이고, 무선랜에 연결하는 코드가 이어집니다.

마지막으로, 날씨 데이터를 요청하고 처리하기 위한 함수인 OpenWeatherMap()을 호출합니다. 리턴값이나 인수는 없습니다. 이 함수로 인해 setup() 함수가 간단해졌습니다.

void loop() {
}
void OpenWeatherMap() { 
}

loop() 함수는 아무 일도 하지 않기에 비워 두었습니다.

그 아래로 OpenWeatherMap() 함수를 정의하는 부분이 나오는데 이전 예제의 대부분의 코드가 이 곳에 들어가게 됩니다.

void OpenWeatherMap() 정의

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=1835847&APPID=???&units=metric&mode=xml HTTP/1.1");
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }
}

함수 정의를 시작합니다. 우선, 메시지 출력 후 openweathermap 서버에 연결합니다. 연결이 설정되면 Request Message를 전송합니다.

이제, 서버로부터 전송되는 메시지가 있는지 체크하고 받아서 처리하는 코드가 올 차례입니다.

이전 예제에선 이 부분이 loop() 함수 안에 있었습니다. 서버와 연결되어 있는지 계속해서 체크하고, 연결된 상태일 때, 버퍼에 도착한 메시지가 있는지 확인해서 읽어 내어 처리하는데, 계속해서 체크하는 이 부분이 loop() 함수 내에 있기에 자동으로 처리되었습니다. 하지만, 이제 따로 함수안에서 처리하기 때문에 반복문이 하나 더 필요합니다.

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=1835847&APPID=???&units=metric&mode=xml HTTP/1.1");
    client.println("Host: api.openweathermap.org");
    client.println("Connection: close");
    client.println();
  }
  while (client.connected()) {  // 연결중일 때만 처리
    while (client.available()) {  // 문자가 도착했을 때만 처리
      char c = client.read();
    }
  }
  Serial.println("disconnecting from server.");
  client.stop();
}

위와 같이 이중 while문 안에서 전송된 데이터를 처리하게 됩니다. 만약 첫 번째 while()문이 없다면, 이 함수는 아무 일도 하지 않을 것입니다. 버퍼에 문자가 도착하기 전에 while (client.available()) 부분을 만나서 while문을 건너 뛰게 됩니다.

바깥쪽 while()문을 빠져나갔다면 서버와의 연결이 끊어진 상태이므로 메시지를 출력하고 client를 종료합니다. 나머지 XML 파싱 코드는 그대로 복사해서 넣으면 됩니다. 또, Start Tag를 처리하기 위한 함수인 startTagProcessing() 함수도 그대로 복사해서 아래쪽에 붙여 주면 우선 이전 소스와 동일한 결과를 출력할 수 있습니다.

수정한 후 실행한 결과이며 이전과 동일한 것을 확인할 수 있습니다. 바로 밑 전체 소스를 참고하세요!

#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() {
  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();
}
void loop() {
}
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=1835847&APPID=7b08dfe35b4273bbe63604c75573cacf&units=metric&mode=xml HTTP/1.1");
    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);
            }
          }
          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) {
      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");
    }
  }
}

서버 관련 코드 추가하기

이제 위 소스에 서버 구현과 관련된 코드들을 추가하겠습니다. 역시 이전에 만들었던 소스를 그대로 붙여 넣기만 하면 되기에 어렵지 않습니다.

#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";
IPAddress ip;
WiFiServer Webserver(80); // Server 서비스를 위한 클래스 인스턴스
WiFiClient client;

전역 선언부에선 딱 두 줄만 추가하면 됩니다.

IP주소를 저장하기 위한 변수 ip를 선언하고, WiFiServer 클래스의 인스턴스로 Webserver를 80포트로 선언합니다. 클라이언트 예제와 서버 예제의 대부분의 코드가 겹치기 때문에 필요한 부분만 추가하면 됩니다. 이번 프로그램은 이전에 작성한 client 예제와 server 예제를 합친 것과 같습니다. 각각 server, client라는 이름의 변수명이 존재했기 때문에, 합칠때 한쪽의 이름들을 변경해줘야 합니다. 그래서, 앞쪽에 Web을 붙여 Webserver로 변경했습니다.

void setup() {
  WiFi.setPins(8,7,4,2); 
  Serial.begin(9600);
  delay(1000);
  ...
  ...
  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);
}

setup() 함수는 위와 같이 추가합니다. begin() 함수를 통해 서버 서비스를 시작하고, IP를 구해서 접속한 주소를 시리얼 모니터에 출력해 줍니다.

WiFiClient Webclient = Webserver.available();
  if (Webclient) {
    Serial.println("new client");
    bool newLine = false;
    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("<title>Network Info</title>");
            Webclient.println("</head>");
            Webclient.println("<body>");
            Webclient.println("<h1>WiFi Connection Status</h1>");
            Webclient.print("<p>IP Address : ");
            Webclient.print(ip);
            Webclient.print("</p>");
            Webclient.print("<p>RSSI : ");
            Webclient.print(WiFi.RSSI());
            Webclient.print(" dBm</p>");
            Webclient.println("</body>");
            Webclient.println("</html>");
            break;
          } else {
            newLine = true;
          }
        } else if (c != '\r') {
          newLine = false;
        }
      }
    } 
    Webclient.stop();
    Serial.println("client disonnected");
  }

loop()함수는 이전 예제에서 그대로 복사해서 붙이고 client변수를 구별해주기 위해 Webclient로 변경합니다.

그리고, 웹페이지를 클라이언트에 전송하기 전에 날씨 데이터를 전송 받기 위해 OpenWeatherMap()함수를 실행하는 코드가 추가되었습니다.

여기 까지 완료하고 실행했을 때의 결과입니다.

보드가 기동하면 무선랜에 접속하고 openweathermap.org에서 날씨 데이터를 가져와 시리얼 모니터에 출력합니다. 그리고, 웹서버를 시작하고 클라이언트 접속을 리스닝합니다. 클라이언트 접속이 발생하면 다시 날씨 데이터를 가져와 출력하고 웹페이지를 서비스합니다.

#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";
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;
    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("<title>Network Info</title>");
            Webclient.println("</head>");
            Webclient.println("<body>");
            Webclient.println("<h1>WiFi Connection Status</h1>");
            Webclient.print("<p>IP Address : ");
            Webclient.print(ip);
            Webclient.print("</p>");
            Webclient.print("<p>RSSI : ");
            Webclient.print(WiFi.RSSI());
            Webclient.print(" dBm</p>");
            Webclient.println("</body>");
            Webclient.println("</html>");
            break;
          } else {
            newLine = true;
          }
        } else if (c != '\r') {
          newLine = false;
        }
      }
    } 
    Webclient.stop();
    Serial.println("client disonnected");
  }
}
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=1835847&APPID=7b08dfe35b4273bbe63604c75573cacf&units=metric&mode=xml HTTP/1.1");
    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);
            }
          }
          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) {
      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");
    }
  }
}

웹페이지에 날씨 정보 출력하기

시리얼 모니터에 출력하던 날씨 정보를 웹페이지에 실어 보낼 차례입니다.

날씨 정보를 추출하는 즉시 바로 시리얼로 출력하던 이제 안되고 중간에 정보를 저장할 변수가 필요합니다. 날씨 정보의 추출은 OpenWeatherMap() 함수안쪽이지만 웹페이지에 출력하는 부분은 함수 바깥쪽이기 때문에 몇 개의 전역 변수를 선언하여 자유롭게 불러 사용할 수 있게 하였습니다.

#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 저장
// 날씨 정보를 저장할 변수들
String country = "";
String cityName = "";
String coordLon = "";
String coordLat = "";
String sunRise = "";
String sunSet = "";
String tempValue = "";
String humidValue = "";
String pressValue = "";

위와 같이 변수들을 추가했습니다. 이 변수들의 이름은 OpenWeatherMap() 함수내에서 선언하여 사용한 변수들과 같습니다. 따라서, 함수 내에서 선언하는 부분만 제거하면 그대로 적용 가능합니다.

if (startTag.indexOf("country") != -1) {
  if (endTag.indexOf("country") != -1) {
    Serial.print("Country : ");
    Serial.println(currentData);
    country = currentData;
  }
}

우선, 국가명을 출력하는 부분을 수정하였습니다. 시리얼 모니터에 출력하는 부분은 모니터링을 위해 남겨 두고 위에서 선언한 country 변수에 국가명을 저장하도록 코드 한 줄을 추가했습니다.

Webclient.print("<p>Country : ");
Webclient.print(country);
Webclient.println("</p>");

그리고, 위와 같이 웹페이지 소스를 추가했습니다. 그리고 위 코드는 아래처럼 한 줄로 바꿀 수 있습니다.

Webclient.println("<p>Country : " + country + "</p>");

print() 함수내의 "+" 연산자는 문자열을 이어주는 역할을 하는데, 두 개의 "+"연산자 양쪽 모두 문자열이기 때문에 사용 가능합니다. 중간에 문자열 타입이 아닌 경우엔 위쪽과 같이 나누어서 출력해야 에러가 없습니다.

void startTagProcessing() {
  if (startTag.startsWith("<city")) {
    int attribName = startTag.indexOf("name=");
    if (attribName != -1) {
      //String cityName = startTag.substring(attribName + 6); 선언 부분이 필요 없음
      cityName = startTag.substring(attribName + 6);
      int quote = cityName.indexOf("\"");
      cityName = cityName.substring(0, quote);
      Serial.println("City : " + cityName);
    }

도시명을 저장하기 위해 위와 같이 변경했습니다. 모든 내용은 동일하고 단지 새로운 변수를 선언하지 않고 바깥쪽에서 선언한 변수를 가져다 쓰게 되므로 "String" 단어 하나만 삭제 하면 됩니다. 주석 처리된 원래의 코드에서 아래쪽 새로운 코드로 변경된 것은 String의 삭제말곤 없습니다.

Webclient.println("<p>Country : " + country + "</p>");
Webclient.print("<p>City Name : " + cityName + "</p>");

역시 웹 태그쪽도 한 줄 추가해 주었습니다. 도시의 위도와 경도를 받아 출력하는 부분도 동일하게 처리하면 되기 때문에 생략하고 일몰, 일출 시간 출력하는 부분으로 넘어 가겠습니다.

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

일출, 일몰 시간은 위와 같이 조합해서 저장해 줍니다. 같은 변수명으로 선언한 부분은 없기 때문에 삭제할 부분이 없습니다.

            Webclient.println("<!DOCTYPE html>");
            Webclient.println("<html>");
            Webclient.println("<head>");
            Webclient.println("<title>openweathermap Weather Info</title>");
            Webclient.println("</head>");
            Webclient.println("<body>");
            Webclient.println("<h1>Weather Info of City</h1>");
            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;

웹 페이지 출력 부분의 수정된 코드입니다. 출력 내용에 맞게 타이틀과 본문 제목도 수정했습니다.

결과 화면을 위와 같이 확인하였습니다. 다음 글에선 몇 개의 도시중에 선택을 받아 해당하는 도시의 날씨 정보를 출력하는 연습을 해보겠습니다. 이상입니다. 아래쪽 전체 소스에서 openweathermap APPID는 노출 방지를 위해 생략하였으므로 꼭 ????? 부분을 본인의 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 저장
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;
    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("<title>openweathermap Weather Info</title>");
            Webclient.println("</head>");
            Webclient.println("<body>");
            Webclient.println("<h1>Weather Info of City</h1>");
            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;
          }
        } else if (c != '\r') {
          newLine = false;
        }
      }
    } 
    Webclient.stop();
    Serial.println("client disonnected");
  }
}
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=1835847&APPID=??????&units=metric&mode=xml HTTP/1.1");
    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