여기서 설명에 사용할 예제는 단순하게 접속하는 클라이언트에게 '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 주소로 바꿔줘야 한다.
정상적으로 동작한다면 웹브라우져에 다음과 같이 출력될 것이다.