2018년 7월 24일 화요일

ESP32용 Asynchronous HTTP 웹서버

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          아두이노 코어를 실행하는 ESP32에서 비동기식(asynchronous) HTTP 웹서버를 설정하는 방법에 대해 설명해 보도록 하겠다.
여기서 설명에 사용할 예제는 단순하게 접속하는 클라이언트에게 'hello world'라는 메시지를 리턴하는 웹서버이다.

여기서 필요한 라이브러리는 ESPAsyncWebServer와 AsyncTCP 두개이다.

ESPAsyncWebServer 라이브러리는 https://github.com/me-no-dev/ESPAsyncWebServer 에서 다운받을 수 있다.

이 라이브러리는 비동기식 HTTP (및 웹소켓) 서버를 설정해서 하나 이상의 클라이언트를 동시에 처리할 수 있게 해 준다. 또한 코드에서 볼 수 있는 것 처럼 서버 콜백 함수만 등록을 해 놓으면 메인루프에서 클라이언트 처리 함수를 주기적으로 호출할 필요가 없다.

AsyncTCP 라이브러리는 ESPAsyncWebServer를 위해 필요한 라이브러리라 필요하긴 하지만 코드에서 이 라이브러리를 직접 사용할 일은 없고 그냥 include만 해 주면 된다. https://github.com/me-no-dev/AsyncTCP 에서 다운받을 수 있다.

이 라이브러리는 ESP32용 비동기 TCP 라이브러리로 ESPAsyncWebServer 라이브러리 구현을 위한 기반이다.

코드를 위해서는 몇 가지 라이브러리를 include 해야 한다.

#include <WiFi.h>
#include <FS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

그 후 공유기 접속을 위한 정보가 따라온다.

const char *ssid = "Your network";
const char *password = "Your password";

또한 AsyncWebServer 클래스의 변수를 선언해 Asynchronous ESP32 HTTP 서버를 설정 하는데 사용한다.

Constructor의 입력으로 몇번 포트를 사용할 것인지를 넘겨준다. 여기서는 HTTP  디폴트 포트인 80번을 사용한다.

AsyncWebServer server(80);

setup() 함수에서는 먼저 기본적인 작업들...시리얼 포트 초기화, 무선랜 접속 등..을 수행한다. 코드는 다음과 같다.

void setup()
{
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println(WiFi.localIP());

이제 HTTP request를 받기 위해 서버가 listen을 할 route를 설정해 주고, 해당 route로 request가 수신될 때 실행될 함수를 만들어 준다.

server 오브젝트의 on 메소드를 호출해 콜백함수를 지정해 준다. on 메소드의 첫번째 파라미터는 패스를 저정하는 문자열이 된다. 여기서는 '/hello' route에 대한 request를 listen하도록 설정할 것이다.

두번째 파라미터는 WebRequestMethod 타입의 enum 값으로 해당 route에 대해 어떤 타입의 HTTP request를 허욜할지 지정한다. 여기서는 HTTP GET request만 받을 것이므로 HTTP_GET 값을 사용한다.

세번째 파라미터는ArRequestHandlerFunction 타입에 의해 signature가 정의된 함수가 온다. ArRequestHandlerFunction 타입 정의는 https://github.com/me-no-dev/ESPAsyncWebServer/blob/63b5303880023f17e1bca517ac593d8a33955e94/src/ESPAsyncWebServer.h 에서 확인할 수 있다.

여기서 지정하는 처리 함수는 파라미터로 AsyncWebServerRequest 타압 오브젝트의 포인터를 넘겨 받고 리턴값은 없다. 각 incoming client는 이 클래스 오브젝트로 wrap 되어 있고 두 오브젝트는 커넥션이 끊어질 때 까지 같이 살아 있다.

이 문법을 간단하게 하기 위해서 이 처리함수를 C++ lambda function으로 선언한다. 그러므로 내부적으로 선언한 이름 없는 함수로 지정할 수 있게 된다. 여러개의 route를 가지는 서버의 경우 이렇게 하는것이 각 route에 대해 각각 이름을 가지는 함수로 선언하는 것 보다 더 깔끔하고 컴팩트 해 진다.

다음과 같은 lambda 문법을 사용할 것이다.

[captures] (params) {body}

여기서는 따로 captures를 사용하지 않을 것이기 때문에 단순히 '[]'를 사용한다. params의 경우 앞에서 언급한 처리 함수의 signature를 따를 필요가 있다. 그러므로 lambda는 AsyncWebServerRequest 타입 오브젝트의 포인터를 파라미터로 받는다.

server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request) {
  // Lambda body implementation
});

이 예제에서는 클라이언트에게 단순히 'hello world'라는 메시지를 돌려주면 된다. 앞에서 언급한 것 처럼 각 클라이언트는 AsyncWebServerRequest 오브젝트에 연결되어 있고, 이 오브젝트는 응답할 HTTP response를 지정할 수 있게 해 주는 send라는 메소드를 가지고 있다.

이 메소드는 첫번째 파라미터로 HTTP response code를 받는데 여기서는 200을 넘겨준다. 이 HTTP response code는 "OK"를 의미한다.

두번쨔 파라미터는 response의 content-type 값이다. 여기서는 'hello world'라는 문자열을 리턴할 것이기 때문에 "text/plain"을 사용하고 있다.

여기서 우리는 오브젝트 자체가 아니고 오브젝트에 대한 포인터를 다루는 것이기 때문에 AsyncWebServerRequest 오브젝트의 send 메소드를 호출할 때 '->' 를 사용해야 한다는 것을 주의해라.

server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request) {
  request->send(200, "text/plain", "Hello World");
});

마지막으로 설정을 끝내기 위해 server 오브젝트의 begin 메소드를 호출해 줘야만 한다. begin 메소드를 호출하면 서버를 시작시킨다.

server.begin();

여기서 만든 서버는 비동기식(asynchronous)이기 때문에 메인루프에서 따로 처리함수등을 호출할 필요가 없다. 위에서 선언한 route 처리함수는 클라이언트에서 request가 들어오면 자동으로 호출된다.

전체 코드는 다음과 같다.

#include <WiFi.h>
#include <FS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

const char *ssid = "Your network";
const char *password = "Your password";

AsyncWebServer server(80);

void setup()
{
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println(WiFi.localIP());
 
  server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "Hello World");
  });
  server.begin();
}

void loop()
{
}



코드 테스트


코드를 컴파일 해서 ESP32에 업로드 해 준다. 시리얼 모니터를 보면 무선랜에 접속되면 ESP32 보드의 IP 주소가 찍힌다.
이제 웹브라우져로 가서 주소창에 다음의 주소를 입력해 주면 된다.

http:[IP address]/hello

여기서 [IP address] 부분을 ESP32 보드의 IP 주소로 바꿔줘야 한다.

정상적으로 동작한다면 웹브라우져에 다음과 같이 출력될 것이다.