강좌 전체보기

.

센서장치에 WiFi 모듈을 붙인다면 대부분 서버와 HTTP 통신을 하기 위해서 일겁니다. 이번 파트에서는 ESP32 모듈을 이용해 외부 서버에 HTTP request 를 보내보도록 하겠습니다.

HTTP request: GET and POST

웹 서버로 HTTP 요청을 보낼 때 두 가지 방식, GET 과 POST 가 많이 사용됩니다. 아주 단순하게 GET 과 POST 의 차이를 살펴보면 다음과 같습니다.

  • GET
    • URL 뒤에 ? 를 붙이고 데이터(파라미터 이름과 값)를 붙여 보냅니다.
    • 예) www.abc.com/test?param1=myname&param2=myemail
    • URL 길이 제한이 있기 때문에 많은 양의 데이터 전송이 어려움. URL 만으로 데이터 송수신이 가능.

  • POST
    • 데이터를  HTTP request 의 Body 에 넣어 보냅니다.
    • HTTP request 의 Header 에 Content-Type 이라는 필드를 넣고 Body 에 들어갈 데이터가 무엇인지 설명합니다.
    • 길이 제한이 없고 다양한 데이터 전송 가능. URL 에 데이터가 노출되지 않음.

여기서도 이 두가지 방식으로 HTTP request 를 보내도록 하겠습니다.

HTTP request: GET

먼저 GET 방식으로 HTTP 요청을 보내보겠습니다. 아래에서 스케치를 받은 뒤, ssid, password 변수를 공유기 설정에 맞게 변경하세요.

ESP32 에 업로드 하고 [시리얼 모니터]를 실행하면 아래와 같은 결과를 볼 수 있습니다.

이 예제는 아래 URL 로 HTTP request 를 계속 보내고 그에 대한 응답을 받아오는 예제입니다.

  • http://jsonplaceholder.typicode.com/comments?id=10
    • 서버 ==> jsonplaceholder.typicode.com
    • 서버내의 경로 ==> /comments
    • 파라미터 ==> [key: id], [value: 10]

의역하자면 id 가 10 인 comment 를 응답으로 보내달라는 HTTP request 입니다. 이 요청에 대한 응답을 받아 시리얼 모니터에 표시한겁니다.

시리얼 모니터에 표시된 내용 중 200 은 응답코드입니다. 200은 모든 것이 정상 처리되었다는 의미입니다. (기타 응답코드는 링크 참고)

그리고 200 아래에 표시되는 내용들은 우리가 요청한 comment 의 상세 정보를 JSON 데이터 형식으로 보내준 것입니다. JSON 은 서버 API 를 호출하거나 그에 대한 응답을 받을 때 자주 사용하는 데이터 형식입니다. JSON 에 대한 상세 설명은 이 강좌의 범위를 벗어나므로 링크의 내용을 참고하세요.

그럼 우리가 테스트 해 본 소스코드를 살펴보겠습니다.

먼저 초기화 함수인 setup() 부터 보겠습니다.

const char* ssid = "yourNetworkName";
const char* password =  "yourPassword";
 
void setup() {
 
  Serial.begin(115200);
  delay(4000);
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }
 
  Serial.println("Connected to the WiFi network");
 
}

WiFi.begin() 호출을 통해 공유기에  연결하고, 연결이 완료되길 기다리는게 전부입니다.  물론 ssid, password 설정이 제대로 되어 있어야겠죠.

다음으로 메인 루프 역할을 하는 loop() 함수입니다.

void loop() {
 
  if ((WiFi.status() == WL_CONNECTED)) { //Check the current connection status
 
    HTTPClient http;
 
    http.begin("http://jsonplaceholder.typicode.com/comments?id=10"); //Specify the URL
    int httpCode = http.GET();                                        //Make the request
 
    if (httpCode > 0) { //Check for the returning code
 
        String payload = http.getString();
        Serial.println(httpCode);
        Serial.println(payload);
    }
    else {
      Serial.println("Error on HTTP request");
    }
 
    http.end(); //Free the resources
  }
 
  delay(10000);
}

HTTP request 를 보내기 위해 HTTPClient 클래스를 이용합니다. HTTPClient.begin(URL_string) 을 호출하고  HTTPClient.GET() 만 호출해주면 GET 방식으로 request 를 전송합니다. 그리고 response 를 받을 때 까지 blocking 됩니다.

    http.begin("http://jsonplaceholder.typicode.com/comments?id=10"); //Specify the URL
    int httpCode = http.GET();

Response를 받으면 아래 구문이 실행됩니다. 단순히 response 의 body 에 담겨져온 내용을 시리얼로 출력해 줬습니다.

    if (httpCode > 0) { //Check for the returning code
        String payload = http.getString();
        Serial.println(httpCode);
        Serial.println(payload);
    }

이처럼 HTTPClient 클래스를 이용하면 HTTP 프로토콜/메시지 구조를 모르더라도 손쉽게 HTTP request 를 처리할 수 있습니다.

HTTP request: GET – JSON 처리

HTTP GET 예제에서 response 데이터가 JSON 형태의 string 이었습니다. 만약 우리가 일반 HTML 페이지에 GET 요청을 보냈다면 response 데이터는 HTML string 이었을 겁니다.

하지만 센서장치가 브라우저를 탑재한 것도 아니고 HTML 응답을 위해 HTTP 요청을 보내지는 않겠죠. 보통은 서버에 데이터를 전송하거나 받기 위해 HTTP 요청을 보낼 것입니다. 그리고 서버에 따라 다르겠지만 많은 경우 JSON 형태로 데이터를 주고 받습니다.

따라서 앞선 예제처럼 JSON string 으로 응답을 받은 경우 JSON string 을 소스코드에서 인식하기 좋게 파싱해서 사용해야 합니다. 이번 실습은 HTTP GET 예제에서 JSON 응답을 파싱해서 출력하도록 하겠습니다.

파싱을 위해 아두이노용 JSON 파싱 라이브러리를 설치해야 합니다. 아두이노 개발환경에서 [상단 메뉴 – 스케치 – 라이브러리 포함하기 – 라이브러리 관리] 를 누릅니다. 그리고 상단에 ArduinoJson 을 입력하고 검색을 합니다. 그럼 ArduinoJson 라이브러가 보일겁니다. 우측 아래 버튼을 눌러 설치하면 됩니다.

  • 주의!!ArduinoJson 최신 Beta 버전을 설치하지 마세요. 안정화된 버전을 설치해서 사용해야 합니다. 이 문서에서 사용한 버전은 5.13.2 입니다.

아래에서 스케치를 받아 HTTP GET 예제와 같은 방식으로 테스트하면 됩니다.

테스트 결과가 아래처럼 나오면 됩니다.

JSON string 을 파싱해서 postId, id, name, email, body 파라미터에 값을 분리해서 넣었음을 알 수 있습니다.

HTTP response 로 받는 JSON string 은 아래와 같습니다.

[
  {
    "postId": 1,
    "id": 1,
    "name": "id labore ex et quam laborum",
    "email": "Eliseo@gardner.biz",
    "body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium"
  },
  {
    "postId": 1,
    "id": 2,
    "name": "quo vero reiciendis velit similique earum",
    "email": "Jayne_Kuhic@sydney.com",
    "body": "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et"
  },
  {
    "postId": 1,
    "id": 3,
    "name": "odio adipisci rerum aut animi",
    "email": "Nikita@garfield.biz",
    "body": "quia molestiae reprehenderit quasi aspernatur\naut expedita occaecati aliquam eveniet laudantium\nomnis quibusdam delectus saepe quia accusamus maiores nam est\ncum et ducimus et vero voluptates excepturi deleniti ratione"
  },
  {
    "postId": 1,
    "id": 4,
    "name": "alias odio sit",
    "email": "Lew@alysha.tv",
    "body": "non et atque\noccaecati deserunt quas accusantium unde odit nobis qui voluptatem\nquia voluptas consequuntur itaque dolor\net qui rerum deleniti ut occaecati"
  },
  {
    "postId": 1,
    "id": 5,
    "name": "vero eaque aliquid doloribus et culpa",
    "email": "Hayden@althea.biz",
    "body": "harum non quasi et ratione\ntempore iure ex voluptates in ratione\nharum architecto fugit inventore cupiditate\nvoluptates magni quo et"
  }
]

Root element 로 배열이 들어가 있습니다. 그리고 배열 안에 Object 가 1 개 이상 나오는 구조입니다. 이걸 처리하는 아두이노 스케치 코드는 아래와 같습니다.

    if (httpCode > 0) { //Check for the returning code
      String payload = http.getString();

      StaticJsonBuffer<500> JSONBuffer; //Memory pool
      JsonArray &root = JSONBuffer.parseArray(payload);
      for(int i=0; i<root.size(); i++) {
        JsonObject& parsed = root[i];
        const char * postId = parsed["postId"]; // Get postId
        const char * id = parsed["id"];         // Get id
        const char * tname = parsed["name"];    // Get name
        const char * email = parsed["email"];   // Get email
        const char * body = parsed["body"];     // Get body

        Serial.println(httpCode);
        Serial.print("postId: "); Serial.println(postId);
        Serial.print("id: "); Serial.println(id);
        Serial.print("tname: "); Serial.println(tname);
        Serial.print("email: "); Serial.println(email);
        Serial.print("body: "); Serial.println(body);
        Serial.println("------------------------------");
        Serial.println();
      }
    }

JSON string 의 root element 가 배열입니다. 그래서 파싱할 때 JSONBuffer.parseArray(payload) 를 사용했습니다. 그리고 JsonArray 포인터를 얻습니다. 이제 JsonArray 에 포함된 object 수만큼 for 루프를 돌면서 Object 를 하나씩 꺼냅니다. 그리고 Object 에 담긴 parameter 들을 가져와서 출력했습니다.

만약 JSON string 의 root element 가 배열이 아니라 Object 라면, JSONBuffer.parseObject(payload) 를 사용하면 됩니다.

HTTP request: POST

이번에는 POST 방식을 사용해서 HTTP request 를 보내는 예제입니다. 이런 HTTP POST 를 테스트할 수 있도록 해주는 jsonplaceholder.typicode.com 서버가 있습니다. 이 서버에 게시글을 등록하는 HTTP request 를 보내겠습니다.

아래에서 스케치를 받아 HTTP GET 예제와 같은 방식으로 테스트하면 됩니다.

아래처럼 결과가 나오면 성공한 것입니다.

201 은 “created” 라는 의미를 가진 서버 응답코드 입니다. 즉, 우리가 보낸 HTTP request 에 담긴 데이터로 새로운 글을 작성했다는 의미입니다. 그리고 HTTP response 에는 작성된 글의 ID 값이 JSON 형태로 들어있습니다.

소스코드를 살펴보면… 초기화 함수 setup() 의 진행은 동일합니다.

loop() 에서의 처리만 확인하면 됩니다.

void loop() {
  if(WiFi.status()== WL_CONNECTED){   //Check WiFi connection status
    HTTPClient http;   
 
    http.begin("http://jsonplaceholder.typicode.com/posts");  //Specify destination for HTTP request

    http.addHeader("Content-Type", "text/plain");             //Specify content-type header
    int httpResponseCode = http.POST("POSTING from ESP32");   //Send the actual POST request
 
    if(httpResponseCode>0){
      String response = http.getString();                       //Get the response to the request
      Serial.println(httpResponseCode);   //Print return code
      Serial.println(response);           //Print request answer
    } else {
      Serial.print("Error on sending POST: ");
      Serial.println(httpResponseCode);
    }
    http.end();  //Free resources
  } else {
    Serial.println("Error in WiFi connection");   
  }
  delay(10000);  //Send a request every 10 seconds
}

POST 방식도 HTTPClient 클래스를 이용합니다. http.begin() 을 호출하면서 우리가 접근할 URL 을 입력합니다.

    HTTPClient http;   
 
    http.begin("http://jsonplaceholder.typicode.com/posts");  //Specify destination for HTTP request

그리고 http.POST() 를 호출할 때 우리가 전송할 텍스트 데이터를 입력합니다. 우리는 서버에 게시할 글의 내용을 보내야 하기 때문에 단순히 텍스트만 HTTP – body 에 넣으면 됩니다. 그리고 HTTP – body 에 담긴 데이터가 어떤 타입의 데이터인지 서버가 알 수 있도록 해줘야 합니다. http.addHeader() 의 역할이 그것입니다. HTTP – body 에 담긴 데이터가 text/plain 타입임을 헤더에 적었습니다. (더 자세한 내용은 Mime type 을 검색하세요)

    http.addHeader("Content-Type", "text/plain");             //Specify content-type header
    int httpResponseCode = http.POST("POSTING from ESP32");   //Send the actual POST request

http.POST() 함수는 blocking call 입니다. 서버에서 응답이 올때까지 코드 진행은 여기서 멈춥니다. 이후 응답이 오면 다음 코드가 진행됩니다. 나머지의 진행은 HTTP GET 테스트 때와 같습니다.

활용

예제를 실행하고 결과를 확인하는 과정은 간단하지만, 이런 코드를 처음 접한다면 꽤 어렵게 느껴질 수도 있습니다. 하지만 이 방법들만 잘 익혀두고 변형해서 사용할 수 있다면, ESP32 모듈을 이용해서 인터넷 어떤 서버든 연결 할 수 있는 강력한 무기를 가지게 되는 것입니다!

참고자료

주의!!! [사물 인터넷 네트워크와 서비스 구축 강좌] 시리즈 관련 문서들은 무단으로 내용의 일부 또는 전체를 게시하여서는 안됩니다. 계속 내용이 업데이트 되는 문서이며, 문서에 인용된 자료의 경우 원작자의 라이센스 문제가 있을 수 있습니다.

강좌 전체보기

.