강좌 전체보기

.

HTTP request 를 통해 웹 서버에 접속하는 것만으로도 센서장치의 가치는 엄청나게 높아집니다. 그런데 여기 또 하나 고려해볼만한 기능이 있습니다. 바로 WiFi  모듈을 작은 웹 서버처럼 동작시키는 것입니다.  외부에서 WiFi 모듈로 HTTP request 를 보내면, WiFi 모듈이 이 요청을 처리하고 응답을 보내는 방식입니다.

이런 마이크로 웹 서버를 WiFi 모듈에 구현하면, 굳이 웹 서버를 거치지 않고 바로 모바일이나 PC와 연동해서 동작할 수 있는 장점이 생깁니다. 경우에 따라서는 WiFi 모듈끼리 데이터를 주고 받을 수도 있구요.

이번 파트에서는 ESP32 모듈을 웹 서버로 구동시키는 몇 가지 방법을 살펴보겠습니다.

.

.

Basic Web-Server example

ESP32를 서버로 사용하는 여러 방법, 라이브러리가 있지만 여기서는 막강한 기능을 가진 ESPAsyncWebServer 를 사용하겠습니다.

아래 순서대로 두 개의 라이브러리를 설치해주세요.

  • ESPAsyncWebServer
    • 비동기 HTTP 서버를 구성할 수 있는 라이브러리
    • 아래의 주소에서 소스를 다운로드
    • https://github.com/me-no-dev/ESPAsyncWebServer
    • 다운받은 소스를 [c:\사용자\사용자명\문서\Arduino\libraries] 라이브러리 폴더에 복사
    • 서버에서 ZIP 파일로 다운을 받은 경우, 압축 해제 후 생성된 폴더의 이름에서 ‘-master’를 제거 후 사용하세요
  • AsyncTCP
    • 비동기 TCP 라이브러리
    • ESPAsyncWebServer를 사용하기 위해 필요한 라이브러리 (우리가 직접 사용하는 라이브러리가 아님)
    • 아래의 주소에서 소스를 다운로드
    • https://github.com/me-no-dev/AsyncTCP
    • ESPAsyncWebServer와 동일하게 라이브러리에 추가

두 라이브러리 설치가 완료되면 아두이노 개발환경을 열어 아래의 코드를 넣으세요.

업로드 후 [시리얼 모니터] 창을 여세요. 그리고 공유기에 연결이 되면 표시되는 IP address 를 확인해야 합니다.

ESP32 모듈이 공유기 접속 후 IP: 192.168.1.12 를 할당 받았네요. 이제 같은 공유기에 연결되어 있는 PC 또는 핸드폰에서 이 주소로 연결해보면 됩니다. 브라우저를 실행하고 아래 URL 을 사용해 보세요.

  • http://192.168.x.x
  • http://192.168.x.x/get
  • http://192.168.x.x/get?message=your_message
  • http://192.168.x.x/post

실행하면 아래와 같이 결과가 뜰겁니다.

이 응답은 ESP32 모듈이 웹 서버로 동작하면서 우리가 보낸 HTTP request 를 받아 처리한 뒤, HTTP response 를 보내준 것입니다.

이번 예제에서는 단순하게 텍스트만 HTTP response 응답에 실어 보내줬는데,  HTML 코드를 보내줘서 웹 페이지를 보여줄 수도 있고, JSON 형식의 데이터를 보내줘서 ESP32 모듈이 마치 API 서버처럼 동작하게 만들 수도 있습니다. 이 마이크로 서버를 어떻게 활용할지는 여러분의 기획과 상상에 달려있습니다.

그런데 post 예제는 아마 [404 Not found] 메시지가 표시될 것입니다. ESP32 서버가 해당 URL 로 받은 HTTP request 가 POST 요청이 아닌경우 일부러 에러 메시지를 리턴해 준 것입니다. 이런 경우 POST 요청을 보내줄 수 있는 어플리케이션을 설치해서 ESP32 IP 로 POST request 를 던져보세요.

POST 방식으로 요청을 보낼 때 HTTP body 에 [message=hello] 이런식으로 데이터를 넣어주면 됩니다.

동작 시나리오는 확인해봤으니 소스코드를 확인해 보겠습니다. 메인 반복 함수는 loop() 는 아무 내용이 없으니 setup() 만 확인하면 됩니다.

.

AsyncWebServer server(80);

const char* ssid = "your_ssid";
const char* password = "your_password";

const char* PARAM_MESSAGE = "message";

void notFound(AsyncWebServerRequest *request) {
    request->send(404, "text/plain", "Not found");
}

void setup() {

    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.printf("WiFi Failed!\n");
        return;
    }

    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());

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

    // Send a GET request to <IP>/get?message=<message>
    server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
        String message;
        if (request->hasParam(PARAM_MESSAGE)) {
            message = request->getParam(PARAM_MESSAGE)->value();
        } else {
            message = "No message sent";
        }
        request->send(200, "text/plain", "Hello, GET: " + message);
    });

    // Send a POST request to <IP>/post with a form field message set to <message>
    server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
        String message;
        if (request->hasParam(PARAM_MESSAGE, true)) {
            message = request->getParam(PARAM_MESSAGE, true)->value();
        } else {
            message = "No message sent";
        }
        request->send(200, "text/plain", "Hello, POST: " + message);
    });

    server.onNotFound(notFound);

    server.begin();
}

우리가 사용할 AsyncWebServer 가 80번 포트에서 동작하도록 설정해 줬습니다.

.

AsyncWebServer server(80);

.

setup() 함수 안에서도 공유기에 연결하는 부분은 따로 설명이 필요 없겠죠. WiFi 연결 설정이 끝나면 바로 server.on() 호출이 3번 나옵니다.

[http://esp32_ip/] 주소로 GET 요청이 들어왔을 때 어떻게 처리할지 지정한 코드는 아래와 같습니다.

.

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

.

이 페이지는 파라미터를 받지도 않고, 고정된 응답 – “Hello, world” 문자열을 리턴해주는게 전부입니다.

[http://esp32_ip/get] 주소로 GET 요청이 들어왔을 때의 처리 코드입니다.

    // Send a GET request to <IP>/get?message=<message>
    server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
        String message;
        if (request->hasParam(PARAM_MESSAGE)) {
            message = request->getParam(PARAM_MESSAGE)->value();
        } else {
            message = "No message sent";
        }
        request->send(200, "text/plain", "Hello, GET: " + message);
    });

.

request->hasParam() 호출을 통해 URL 에 붙어 들어온 파라미터가 있는지 확인합니다. 만약 있으면 값을 추출해서 HTTP response 를 보낼때 HTTP body 에 담아서 보내줍니다. 그럼 상대방 브라우저에 보내준 문자가 표시되겠지요.

즉, 사용자가 PC에서 [http://esp32_ip/get?message=hihihi]  URL로 GET 요청을 보내면 HTTP response 에 hihihi 문자열이 담겨서 들어옵니다. 그래서 브라우저에 [Hello, GET: hihihi] 가 표시됩니다. 내가 보낸 메시지를 되돌려주는 에코 페이지 같은겁니다.

마지막은 POST 요청을 처리하는 [http://esp32_ip/post] 입니다.

    // Send a POST request to <IP>/post with a form field message set to <message>
    server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
        String message;
        if (request->hasParam(PARAM_MESSAGE, true)) {
            message = request->getParam(PARAM_MESSAGE, true)->value();
        } else {
            message = "No message sent";
        }
        request->send(200, "text/plain", "Hello, POST: " + message);
    });

.

GET 요청을 처리할 때와 유사하게 동작합니다.

마지막으로 서버가 제공하지 않는 URL로 접근했을 때의 HTTP response 를 만들어주는 [404 Not found] 콜백함수를 등록해주고 서버를 동작시킵니다.

    server.onNotFound(notFound);
    server.begin();

.

.

WebServer + File system(SPIFFS)

앞선 예제에서는 HTTP response 로 보내줄 데이터를 string 형태로 직접 소스코드 상에서 만들어 썼습니다. 하지만 만약 HTML 코드처럼 길이가 길고 복잡한 파일을 보내거나, 하나의 HTML 에서 다양한 리소스를 참고하기 때문에 해당 리소스도 함께 보내줘야 한다면… 이걸 일일이 소스코드에서 다루기 너무 힘듭니다. 이런 경우 File system 과 연동해서 동작하면 HTTP response 로 보내줄 리소스를 효율적으로 관리할 수 있습니다.

PC에서 C/D 드라이브의 파일을 관리하듯 ESP32 도 플래시 메모리에 간단한 파일 시스템을 구축할 수 있습니다. 이 기능을 SPIFFS (Serial Peripheral Interface Flash File System) 라 부릅니다. 흔히 사용하는 ESP32 Dev Module 은 대부분 4MByte 의 플래시 메모리가 있는데 이 중 일부를 파일 시스템으로 사용하는 것입니다.

SPIFFS 를 사용하기 위한 라이브러리는 ESP32 개발환경에 이미 포함되어 있습니다. 하지만 PC 에 미리 폴더와 파일 구조를 만들어서 ESP32 플래시 메모리에 써줘야 하는데, 이 작업은 별도의 툴을 설치해야 합니다.

아래 링크에서 ESP32FS-v0.1.zip 파일을 받으세요.

  • ESP32FS plugin download
  • 압축 해제 후 [ESP32FS\tool\esp32fs.jar] 파일이 위치하도록 만듭니다. ESP32FS 폴더를 복사합니다.
  • [C:\Program Files (x86)\Arduino\tools] 경로에 ESP32FS 폴더를 붙여넣으세요.
    • 맥 OS의 경우 [~/Documents/Arduino/] 폴더 안에 tools 폴더를 만들어 사용
  • 아두이노 개발환경 재시작

이제 SPIFFS 파일을 플래시에 업로드 할 수 있는 준비는 다 되었습니다. 실제 코드를 올려 테스트 해보겠습니다. 아래 링크에서 예제 파일을 받으세요.

이번 예제에는 스케치 파일 뿐 아니라 ESP32 에 올릴 폴더와 파일 구조도 포함되어 있습니다. 웹 서버로 동작할 때 client 에 제공할 html 파일과 이미지가 data 폴더에 포함되어 있습니다.

일단 스케치 파일 [ESP32_WiFi_WebServer_SPIFFS.ino] 을 아두이노 개발환경에서 열어보세요. 스케치를 업로드 하기 전에 ESP32 플래시 메모리에 파일들 부터 올립니다.

  • 시리얼 모니터 창이 열려 있다면 모두 닫아주세요. File upload 를 위해 COM 포트를 비워둬야 합니다.
  • [메뉴 – 툴 – ESP32 Sketch Data Upload] 메뉴를 눌러 File upload 를 실행합니다.
    • 이 메뉴는 현재 스케치 폴더 안에 있는 data 폴더를 찾아서 그 안에 들어있는 폴더와 파일 구조대로 ESP32 플래시 메모리에 써줍니다.

이 작업이 끝났으면 이제 스케치 파일을 컴파일해서 올리면 됩니다.

스케치 업로드까지 끝났으면 PC에서 브라우저를 실행해서 ESP32 모듈의 IP address 로 접속해보세요. 아래처럼 HTML 페이지가 나올겁니다.

우리가 파일 시스템에 넣어둔 HTML 파일과 JPG 이미지 파일을 이용해 웹 화면을 제공하는 서버로 만든겁니다!

사용된 소스코드도 그닥 어렵지 않습니다.

.

AsyncWebServer server(80);

const char* ssid = "your_ssid";
const char* password = "your_pass";


void notFound(AsyncWebServerRequest *request) {
    request->send(404, "text/plain", "Not found");
}



void setup() {

    Serial.begin(115200);
    SPIFFS.begin(true);
    
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.printf("WiFi Failed!\n");
        return;
    }

    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(SPIFFS, "/index.html");
    });

    server.on("/image/bg.jpg", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(SPIFFS, "/image/bg.jpg");
    });

    server.on("/image/member.jpg", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(SPIFFS, "/image/member.jpg");
    });

    server.onNotFound(notFound);
    server.begin();
}



void loop() {
}

.

server.on() 함수를 이용해 특정 URL에 대한 처리 방법을 명시할 때 SPIFFS 에서 파일을 읽어와서 제공해주도록 해준겁니다. 위에서 확인했던 홈페이지 화면은 1 개의 HTML 파일과 2개의 JPG 파일을 사용한 것입니다. 그래서 총 3개의 리소스에 대한 처리 방법을 명시해주면 됩니다.

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(SPIFFS, "/index.html");
    });

    server.on("/image/bg.jpg", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(SPIFFS, "/image/bg.jpg");
    });

    server.on("/image/member.jpg", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(SPIFFS, "/image/member.jpg");
    });

.

이런식으로 사용할 화면과 파일을 늘려가면 꽤 복잡한 사이트 구조도 만들 수 있습니다.

.

.

활용

ESP32 모듈의 성능이 꽤나 준수하기 때문에 마이크로 웹 서버로 돌리기에 모자람이 없습니다. 특히 자바 스크립트, HTML, CSS 등을 다룰 줄 안다면 도저히 센서장치가 제공하는 웹 화면이라 믿지 않을만큼 훌륭한 화면을 제공할 수 있습니다. 혹은 API 서버처럼 동작하면서 주변에 있는 센서장치들을 관리하고 상호 연동하도록 만들수도 있습니다.

그래서 마이크로 웹 서버를 만드는 예제는 꼭 직접 실습해보고 코드를 눈여겨 봐둘 필요가 있습니다.

참고자료

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

.

강좌 전체보기

.