ESP32

ESP32 WebSocket 서버와 제어 출력

지구빵집 2025. 1. 9. 11:19
반응형

 

 

ESP32 웹소켓 서버: 제어 출력(Arduino IDE)

 

이 튜토리얼에서는 웹소켓 통신 프로토콜을 사용하여 ESP32로 웹 서버를 빌드하는 방법을 알아봅니다. 예를 들어, ESP32 출력을 원격으로 제어하는 ​​웹 페이지를 빌드하는 방법을 보여드리겠습니다. 출력 상태는 웹 페이지에 표시되고 모든 클라이언트에서 자동으로 업데이트됩니다.

 

ESP32 웹소켓 서버 제어 출력 Arduino IDE

 

ESP32는 Arduino IDE와 ESPAsyncWebServer를 사용하여 프로그래밍됩니다. ESP8266에 대한 유사한 웹소켓 가이드도 있습니다.

 

이와 유사한 이전 웹 서버 프로젝트 중 일부를 따라왔다면 여러 탭(동일한 장치 또는 다른 장치)을 동시에 열면 웹 페이지를 새로 고치지 않는 한 모든 탭에서 상태가 자동으로 업데이트되지 않는다는 것을 알아차렸을 것입니다. 이 문제를 해결하기 위해 웹소켓 프로토콜을 사용할 수 있습니다. 변경 사항이 발생하면 모든 클라이언트에 알림을 보내고 그에 따라 웹 페이지를 업데이트할 수 있습니다.

 

이 튜토리얼은 독자 중 한 명(Stéphane Calderoni)이 만들고 기록한 프로젝트를 기반으로 합니다. 여기에서 그의 훌륭한 튜토리얼을 읽을 수 있습니다.

 

WebSocket 소개

 

WebSocket은 클라이언트와 서버 간의 지속적인 연결로, TCP 연결을 사용하여 두 당사자 간의 양방향 통신을 허용합니다. 즉, 언제든지 클라이언트에서 서버로, 서버에서 클라이언트로 데이터를 보낼 수 있습니다.

 

ESP32 ESP8266 WebSocket 서버 작동 원리

 

클라이언트는 WebSocket 핸드셰이크라는 프로세스를 통해 서버와 WebSocket 연결을 설정합니다. 핸드셰이크는 HTTP 요청/응답으로 시작하여 서버가 동일한 포트에서 HTTP 연결과 WebSocket 연결을 모두 처리할 수 있도록 합니다. 연결이 설정되면 클라이언트와 서버는 풀 듀플렉스 모드로 WebSocket 데이터를 보낼 수 있습니다.

 

WebSockets 프로토콜을 사용하면 서버(ESP32 보드)가 요청 없이 클라이언트 또는 모든 클라이언트에게 정보를 보낼 수 있습니다. 이를 통해 변경 사항이 발생할 때 웹 브라우저로 정보를 보낼 수도 있습니다.

 

이 변경은 웹 페이지에서 발생한 일(버튼을 클릭)이거나 ESP32 측에서 발생한 일(예: 회로의 실제 버튼을 누르는 것)일 수 있습니다.

 

프로젝트 개요

 

이 프로젝트를 위해 빌드할 웹 페이지는 다음과 같습니다.

 

ESP32 WebSocket 서버 토글 출력 프로젝트 개요

 

  • ESP32 웹 서버는 GPIO 2의 상태를 토글하는 버튼이 있는 웹 페이지를 표시합니다.
  • 단순화를 위해 온보드 LED인 GPIO 2를 제어합니다. 이 예를 사용하여 다른 GPIO를 제어할 수 있습니다.
  • 인터페이스는 현재 GPIO 상태를 보여줍니다. GPIO 상태가 변경될 때마다 인터페이스가 즉시 업데이트됩니다.
  • GPIO 상태는 모든 클라이언트에서 자동으로 업데이트됩니다. 즉, 동일한 기기나 다른 기기에서 여러 개의 웹 브라우저 탭을 열면 모두 동시에 업데이트됩니다.

 

작동 원리는?

 

다음 이미지는 "토글" 버튼을 클릭하면 어떤 일이 발생하는지 설명합니다.

 

ESP32 WebSocket 웹 서버 모든 클라이언트 업데이트 작동 방식

 

"토글" 버튼을 클릭하면 다음과 같은 일이 발생합니다.

 

  1. "토글" 버튼을 클릭합니다.
  2. 클라이언트(브라우저)가 "토글" 메시지와 함께 WebSocket 프로토콜을 통해 데이터를 보냅니다.
  3. ESP32(서버)가 이 메시지를 수신하여 LED 상태를 토글해야 한다는 것을 알고 있습니다. 이전에 LED가 꺼져 있었다면 켭니다.
  4. 그런 다음 WebSocket 프로토콜을 통해 모든 클라이언트에 새 LED 상태와 함께 데이터를 보냅니다.
  5. 클라이언트가 메시지를 수신하고 웹 페이지에서 LED 상태를 적절히 업데이트합니다. 이렇게 하면 변경 사항이 발생할 때 모든 클라이언트를 거의 즉시 업데이트할 수 있습니다.

 

Arduino IDE 준비

 

Arduino IDE를 사용하여 ESP32 보드를 프로그래밍하므로 Arduino IDE에 설치되어 있는지 확인하십시오.

 

Arduino IDE(Windows, Mac OS X, Linux)에 ESP32 보드 설치

 

라이브러리 설치 - 비동기 웹 서버

 

웹 서버를 빌드하려면 ESPAsyncWebServer 라이브러리를 사용합니다. 이 라이브러리는 제대로 작동하려면 AsyncTCP 라이브러리가 필요합니다. 아래 링크를 클릭하여 라이브러리를 다운로드하세요.

 

ESPAsyncWebServer

AsyncTCP

 

이러한 라이브러리는 Arduino 라이브러리 관리자를 통해 설치할 수 없으므로 라이브러리 파일을 Arduino 설치 라이브러리 폴더에 복사해야 합니다. 또는 Arduino IDE에서 Sketch > 라이브러리 포함 > .zip 라이브러리 추가로 이동하여 방금 다운로드한 라이브러리를 선택할 수 있습니다.

 

ESP32 WebSocket 서버 코드

 

다음 코드를 Arduino IDE에 복사합니다.

 

다음 변수에 네트워크 자격 증명을 삽입하면 코드가 바로 작동합니다.

 

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-websocket-server-arduino/
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

// Import required libraries
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

bool ledState = 0;
const int ledPin = 2;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
  html {
    font-family: Arial, Helvetica, sans-serif;
    text-align: center;
  }
  h1 {
    font-size: 1.8rem;
    color: white;
  }
  h2{
    font-size: 1.5rem;
    font-weight: bold;
    color: #143642;
  }
  .topnav {
    overflow: hidden;
    background-color: #143642;
  }
  body {
    margin: 0;
  }
  .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto;
  }
  .card {
    background-color: #F8F7F9;;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
    padding-top:10px;
    padding-bottom:20px;
  }
  .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #fff;
    background-color: #0f8b8d;
    border: none;
    border-radius: 5px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
   }
   /*.button:hover {background-color: #0f8b8d}*/
   .button:active {
     background-color: #0f8b8d;
     box-shadow: 2 2px #CDCDCD;
     transform: translateY(2px);
   }
   .state {
     font-size: 1.5rem;
     color:#8c8c8c;
     font-weight: bold;
   }
  </style>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
  <div class="topnav">
    <h1>ESP WebSocket Server</h1>
  </div>
  <div class="content">
    <div class="card">
      <h2>Output - GPIO 2</h2>
      <p class="state">state: <span id="state">%STATE%</span></p>
      <p><button id="button" class="button">Toggle</button></p>
    </div>
  </div>
<script>
  var gateway = `ws://${window.location.hostname}/ws`;
  var websocket;
  window.addEventListener('load', onLoad);
  function initWebSocket() {
    console.log('Trying to open a WebSocket connection...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage; // <-- add this line
  }
  function onOpen(event) {
    console.log('Connection opened');
  }
  function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
  }
  function onMessage(event) {
    var state;
    if (event.data == "1"){
      state = "ON";
    }
    else{
      state = "OFF";
    }
    document.getElementById('state').innerHTML = state;
  }
  function onLoad(event) {
    initWebSocket();
    initButton();
  }
  function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
  }
  function toggle(){
    websocket.send('toggle');
  }
</script>
</body>
</html>
)rawliteral";

void notifyClients() {
  ws.textAll(String(ledState));
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      notifyClients();
    }
  }
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
             void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if (ledState){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
  return String();
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  initWebSocket();

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(ledPin, ledState);
}

 

 

다음 변수에 네트워크 자격 증명을 삽입하면 코드가 바로 작동합니다.

 

const char* ssid = "REPLACE_WITH_YOUR_SSID";

const char* password = "REPLACE_WITH_YOUR_PASSWORD";

 

코드 작동 방식

코드 작동 방식을 알아보려면 계속 읽거나 데모 섹션으로 건너뜁니다.

 

라이브러리 가져오기

웹 서버를 빌드하는 데 필요한 라이브러리를 가져옵니다.

 

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

 

네트워크 자격 증명

 

다음 변수에 네트워크 자격 증명을 삽입합니다.

 

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

 

GPIO 출력

 

GPIO 상태를 보관하는 ledState라는 변수와 제어하려는 GPIO를 참조하는 ledPin이라는 변수를 만듭니다. 이 경우 온보드 LED(GPIO 2에 연결됨)를 제어합니다.

 

bool ledState = 0;

const int ledPin = 2;

 

AsyncWebServer 및 AsyncWebSocket

 

포트 80에서 AsyncWebServer 객체를 만듭니다.

 

AsyncWebServer server(80);

 

ESPAsyncWebServer 라이브러리에는 WebSocket 연결을 쉽게 처리할 수 있는 WebSocket 플러그인이 포함되어 있습니다. /ws 경로의 연결을 처리하기 위해 ws라는 AsyncWebSocket 객체를 만듭니다.

 

AsyncWebSocket ws("/ws");

 

웹 페이지 빌드

 

index_html 변수에는 웹 페이지를 빌드하고 스타일을 지정하고 WebSocket 프로토콜을 사용하여 클라이언트-서버 상호 작용을 처리하는 데 필요한 HTML, CSS 및 JavaScript가 포함되어 있습니다.

 

참고: Arduino 스케치에서 사용하는 index_html 변수에 웹 페이지를 빌드하는 데 필요한 모든 것을 배치합니다. HTML, CSS 및 JavaScript 파일을 분리한 다음 ESP32 파일 시스템에 업로드하여 코드에서 참조하는 것이 더 실용적일 수 있습니다.

 

추천 자료: SPIFFS(SPI Flash File System)를 사용하는 ESP32 웹 서버

 

index_html 변수의 내용은 다음과 같습니다.

 

<!DOCTYPE HTML>
<html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
  html {
    font-family: Arial, Helvetica, sans-serif;
    text-align: center;
  }
  h1 {
    font-size: 1.8rem;
    color: white;
  }
  h2{
    font-size: 1.5rem;
    font-weight: bold;
    color: #143642;
  }
  .topnav {
    overflow: hidden;
    background-color: #143642;
  }
  body {
    margin: 0;
  }
  .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto;
  }
  .card {
    background-color: #F8F7F9;;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
    padding-top:10px;
    padding-bottom:20px;
  }
  .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #fff;
    background-color: #0f8b8d;
    border: none;
    border-radius: 5px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
   }
   .button:active {
     background-color: #0f8b8d;
     box-shadow: 2 2px #CDCDCD;
     transform: translateY(2px);
   }
   .state {
     font-size: 1.5rem;
     color:#8c8c8c;
     font-weight: bold;
   }
  </style>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
  <div class="topnav">
    <h1>ESP WebSocket Server</h1>
  </div>
  <div class="content">
    <div class="card">
      <h2>Output - GPIO 2</h2>
      <p class="state">state: <span id="state">%STATE%</span></p>
      <p><button id="button" class="button">Toggle</button></p>
    </div>
  </div>
<script>
  var gateway = `ws://${window.location.hostname}/ws`;
  var websocket;
  function initWebSocket() {
    console.log('Trying to open a WebSocket connection...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage; // <-- add this line
  }
  function onOpen(event) {
    console.log('Connection opened');
  }

  function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
  }
  function onMessage(event) {
    var state;
    if (event.data == "1"){
      state = "ON";
    }
    else{
      state = "OFF";
    }
    document.getElementById('state').innerHTML = state;
  }
  window.addEventListener('load', onLoad);
  function onLoad(event) {
    initWebSocket();
    initButton();
  }

  function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
  }
  function toggle(){
    websocket.send('toggle');
  }
</script>
</body>
</html>

 

CSS

 

태그 사이에 CSS를 사용하여 웹 페이지의 스타일을 지정합니다. 원하는 대로 웹 페이지가 보이도록 자유롭게 변경하세요. 이 웹 페이지의 CSS가 어떻게 작동하는지는 이 WebSocket 튜토리얼과 관련이 없기 때문에 설명하지 않겠습니다.

 

<style>
  html {
    font-family: Arial, Helvetica, sans-serif;
    text-align: center;
  }
  h1 {
    font-size: 1.8rem;
    color: white;
  }
  h2 {
    font-size: 1.5rem;
    font-weight: bold;
    color: #143642;
  }
  .topnav {
    overflow: hidden;
    background-color: #143642;
  }
  body {
    margin: 0;
  }
  .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto;
  }
  .card {
    background-color: #F8F7F9;;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
    padding-top:10px;
    padding-bottom:20px;
  }
  .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #fff;
    background-color: #0f8b8d;
    border: none;
    border-radius: 5px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
   }
   .button:active {
     background-color: #0f8b8d;
     box-shadow: 2 2px #CDCDCD;
     transform: translateY(2px);
   }
   .state {
     font-size: 1.5rem;
     color:#8c8c8c;
     font-weight: bold;
   }
 </style>

 

HTML

 

태그 사이에 사용자에게 표시되는 웹 페이지 콘텐츠를 추가합니다.

 

<div class="topnav">
  <h1>ESP WebSocket Server</h1>
</div>
<div class="content">
  <div class="card">
    <h2>Output - GPIO 2</h2>
    <p class="state">state: <span id="state">%STATE%</span></p>
    <p><button id="button" class="button">Toggle</button></p>
  </div>
</div>

 

"ESP WebSocket Server"라는 텍스트가 있는 제목이 있습니다. 자유롭게 해당 텍스트를 수정하세요.

 

ESP WebSocket Server

 

그런 다음 "Output - GPIO 2"라는 텍스트가 있는 제목 2가 있습니다.

 

<h1>ESP WebSocket Server</h1>

 

그 다음에는 현재 GPIO 상태를 표시하는 문단이 있습니다.

 

<h2>Output - GPIO 2</h2>

 

그 다음에는 현재 GPIO 상태를 표시하는 단락이 있습니다.

 

<p class="state">state: <span id="state">%STATE%</span></p>

 

%STATE%는 GPIO 상태의 플레이스홀더입니다. 웹 페이지를 보낼 때 ESP32에서 현재 값으로 대체됩니다. HTML 텍스트의 플레이스홀더는 % 기호 사이에 있어야 합니다. 즉, 이 %STATE% 텍스트는 실제 값으로 대체되는 변수와 같습니다.

 

클라이언트에 웹 페이지를 보낸 후 GPIO 상태가 변경될 때마다 상태가 동적으로 변경되어야 합니다. WebSocket 프로토콜을 통해 해당 정보를 수신합니다. 그런 다음 JavaScript가 수신된 정보를 처리하여 상태를 적절히 업데이트합니다. JavaScript를 사용하여 해당 텍스트를 처리하려면 텍스트에 참조할 수 있는 ID가 있어야 합니다. 이 경우 ID는 state (<span id=”state”>). 입니다.

 

마지막으로 GPIO 상태를 토글하는 버튼이 있는 문단이 있습니다.

 

<p><button id="button" class="button">Toggle</button></p>

 

 

버튼에 ID를 지정했다는 점에 유의하세요(id="button").

 

JavaScript - WebSocket 처리

 

JavaScript는 script 태그 사이에 있습니다. 브라우저에서 웹 인터페이스가 완전히 로드되면 서버와의 WebSocket 연결을 초기화하고 WebSocket을 통해 데이터 교환을 처리하는 역할을 합니다.

 

<script>
  var gateway = `ws://${window.location.hostname}/ws`;
  var websocket;
  function initWebSocket() {
    console.log('Trying to open a WebSocket connection...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage; // <-- add this line
  }
  function onOpen(event) {
    console.log('Connection opened');
  }

  function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
  }
  function onMessage(event) {
    var state;
    if (event.data == "1"){
      state = "ON";
    }
    else{
      state = "OFF";
    }
    document.getElementById('state').innerHTML = state;
  }

  window.addEventListener('load', onLoad);

  function onLoad(event) {
    initWebSocket();
    initButton();
  }

  function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
  }

  function toggle(){
    websocket.send('toggle');
  }
</script>

 

작동 방식을 살펴보겠습니다.

 

게이트웨이는 WebSocket 인터페이스의 진입점입니다.

 

var gateway = `ws://${window.location.hostname}/ws`;

 

window.location.hostname은 현재 페이지 주소(웹 서버 IP 주소)를 가져옵니다.

websocket이라는 새 전역 변수를 만듭니다.

 

var websocket;

 

웹 페이지가 로드될 때 onload 함수를 호출하는 이벤트 리스너를 추가합니다.

 

window.addEventListener('load', onload);

 

onload() 함수는 initWebSocket() 함수를 호출하여 서버와의 WebSocket 연결을 초기화하고 initButton() 함수를 호출하여 버튼에 이벤트 리스너를 추가합니다.

function onload(event) {
  initWebSocket();
  initButton();
}

 

initWebSocket() 함수는 이전에 정의한 게이트웨이에서 WebSocket 연결을 초기화합니다. 또한 WebSocket 연결이 열리거나 닫히거나 메시지가 수신될 때를 위한 여러 콜백 함수를 할당합니다.

 

function initWebSocket() {
  console.log('Trying to open a WebSocket connection…');
  websocket = new WebSocket(gateway);
  websocket.onopen    = onOpen;
  websocket.onclose   = onClose;
  websocket.onmessage = onMessage;
}

 

연결이 열리면 콘솔에 메시지를 인쇄하고 "hi"라는 메시지를 보냅니다. ESP32는 해당 메시지를 수신하므로 연결이 초기화되었음을 알 수 있습니다.

 

function onOpen(event) {
  console.log('Connection opened');
  websocket.send('hi');
}

 

어떤 이유로 웹 소켓 연결이 닫히면 2000밀리초(2초) 후에 initWebSocket() 함수를 다시 호출합니다.

 

function onClose(event) {
  console.log('Connection closed');
  setTimeout(initWebSocket, 2000);
}

 

setTimeout() 메서드는 지정된 밀리초 후에 함수를 호출하거나 표현식을 평가합니다.

 

마지막으로, 새 메시지를 받을 때 발생하는 일을 처리해야 합니다. 서버(ESP 보드)는 "1" 또는 "0" 메시지를 보냅니다. 수신된 메시지에 따라 상태를 표시하는 문단에 "ON" 또는 "OFF" 메시지를 표시하려고 합니다. id="state"인 태그를 기억하십니까? 해당 요소를 가져와 값을 ON 또는 OFF로 설정합니다.

 

function onMessage(event) {
  var state;
  if (event.data == "1"){
    state = "ON";
  }
  else{
    state = "OFF";
  }
  document.getElementById('state').innerHTML = state;
}

 

initButton() 함수는 id(버튼)로 버튼을 가져오고 'click' 유형의 이벤트 리스너를 추가합니다.

 

function initButton() {
  document.getElementById('button').addEventListener('click', toggle);
}

 

즉, 버튼을 클릭하면 토글 함수가 호출됩니다.

 

토글 함수는 '토글' 텍스트가 있는 WebSocket 연결을 사용하여 메시지를 보냅니다.

 

function toggle(){
  websocket.send('toggle');
}

 

그런 다음 ESP32는 이 메시지를 받을 때 발생하는 일, 즉 현재 GPIO 상태를 토글하는 일을 처리해야 합니다.

 

WebSocket 처리 - 서버

 

이전에 클라이언트 측(브라우저)에서 WebSocket 연결을 처리하는 방법을 살펴보았습니다. 이제 서버 측에서 처리하는 방법을 살펴보겠습니다.

 

모든 클라이언트에 알림

 

notifyClients() 함수는 인수로 전달한 모든 내용을 포함하는 메시지로 모든 클라이언트에 알립니다. 이 경우 변경 사항이 있을 때마다 모든 클라이언트에 현재 LED 상태를 알리고자 합니다.

 

void notifyClients() {
  ws.textAll(String(ledState));
}

 

AsyncWebSocket 클래스는 동시에 서버에 연결된 모든 클라이언트에 동일한 메시지를 보내는 textAll() 메서드를 제공합니다.

 

WebSocket 메시지 처리

 

handleWebSocketMessage() 함수는 WebSocket 프로토콜을 통해 클라이언트에서 새 데이터를 받을 때마다 실행되는 콜백 함수입니다.

 

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      notifyClients();
    }
  }
}

 

"toggle" 메시지를 받으면 ledState 변수의 값을 토글합니다. 또한 notifyClients() 함수를 호출하여 모든 클라이언트에 알립니다. 이렇게 하면 모든 클라이언트에 변경 사항이 알려지고 그에 따라 인터페이스가 업데이트됩니다.

 

if (strcmp((char*)data, "toggle") == 0) {
  ledState = !ledState;
  notifyClients();
}

 

WebSocket 서버 구성

 

이제 WebSocket 프로토콜의 다양한 비동기 단계를 처리하기 위한 이벤트 리스너를 구성해야 합니다. 이 이벤트 핸들러는 다음과 같이 onEvent()를 정의하여 구현할 수 있습니다.

 

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
 void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

 

type 인수는 발생하는 이벤트를 나타냅니다. 다음 값을 사용할 수 있습니다.

 

  • 클라이언트가 로그인한 경우 WS_EVT_CONNECT;
  • 클라이언트가 로그아웃한 경우 WS_EVT_DISCONNECT;
  • 클라이언트에서 데이터 패킷을 수신한 경우 WS_EVT_DATA;
  • ping 요청에 대한 응답으로 WS_EVT_PONG;
  • 클라이언트에서 오류를 수신한 경우 WS_EVT_ERROR.

 

WebSocket 초기화

 

마지막으로 initWebSocket() 함수는 WebSocket 프로토콜을 초기화합니다.

 

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

 

processor()

 

processor() 함수는 HTML 텍스트에서 자리 표시자를 검색하여 웹 페이지를 브라우저로 보내기 전에 원하는 것으로 대체하는 역할을 합니다. 우리의 경우, ledState가 1이면 %STATE% 플레이스홀더를 ON으로 바꿉니다. 그렇지 않으면 OFF로 바꿉니다.

 

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if (ledState){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
}

 

setup()

 

setup()에서 디버깅 목적으로 직렬 모니터를 초기화합니다.

 

Serial.begin(115200);

 

ledPin을 OUTPUT으로 설정하고 프로그램이 처음 시작될 때 LOW로 설정합니다.

 

pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);

 

Wi-Fi를 초기화하고 직렬 모니터에 ESP32 IP 주소를 인쇄합니다.

 

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

// Print ESP Local IP Address
Serial.println(WiFi.localIP());

 

이전에 만든 initWebSocket() 함수를 호출하여 WebSocket 프로토콜을 초기화합니다.

 

initWebSocket();

 

요청 처리

 

루트/URL에서 요청을 받으면 index_html 변수에 저장된 텍스트를 제공합니다. 현재 GPIO 상태로 플레이스홀더를 대체하려면 프로세서 함수를 인수로 전달해야 합니다.

 

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});

 

마지막으로 서버를 시작합니다.

 

server.begin();

 

loop()

 

LED는 loop()에서 물리적으로 제어됩니다.

 

void loop() {
  ws.cleanupClients();
  digitalWrite(ledPin, ledState);
}

 

cleanupClients() 메서드를 모두 호출한다는 점에 유의하세요. 그 이유는 다음과 같습니다(ESPAsyncWebServer 라이브러리 GitHub 페이지에서 설명):

 

브라우저는 JavaScript에서 close() 함수를 호출하더라도 WebSocket 연결을 올바르게 닫지 않는 경우가 있습니다. 이는 결국 웹 서버 리소스를 고갈시키고 서버가 충돌하게 됩니다. 메인 루프()에서 cleanupClients() 함수를 주기적으로 호출하면 최대 클라이언트 수가 초과되면 가장 오래된 클라이언트를 닫아 클라이언트 수를 제한합니다. 이는 매 사이클마다 호출할 수 있지만 전력을 덜 사용하고 싶다면 1초에 한 번 정도 호출하는 것으로 충분합니다.

 

데모

 

ssid 및 password 변수에 네트워크 자격 증명을 삽입한 후 보드에 코드를 업로드할 수 있습니다. 올바른 보드와 COM 포트를 선택했는지 확인하는 것을 잊지 마세요.

 

코드를 업로드한 후 115200의 통신 속도로 직렬 모니터를 열고 온보드 EN/RST 버튼을 누릅니다. ESP IP 주소가 인쇄되어야 합니다.

 

로컬 네트워크에서 브라우저를 열고 ESP32 IP 주소를 입력합니다. 출력을 제어하기 위해 웹 페이지에 액세스할 수 있어야 합니다.

 

 

 

버튼을 클릭하여 LED를 전환합니다. 여러 웹 브라우저 탭을 동시에 열거나 다른 장치에서 웹 서버에 액세스할 수 있으며 LED 상태는 변경 사항이 있을 때마다 모든 클라이언트에서 자동으로 업데이트됩니다.

 

마무리

 

이 튜토리얼에서는 ESP32로 WebSocket 서버를 설정하는 방법을 알아보았습니다. WebSocket 프로토콜은 클라이언트와 서버 간의 풀 듀플렉스 통신을 허용합니다. 초기화 후 서버와 클라이언트는 언제든지 데이터를 교환할 수 있습니다.

 

이는 서버가 무언가 발생할 때마다 클라이언트에 데이터를 보낼 수 있기 때문에 매우 유용합니다. 예를 들어, 이 설정에 실제 버튼을 추가하여 누르면 모든 클라이언트에 웹 인터페이스를 업데이트하도록 알릴 수 있습니다.

 

이 예에서는 ESP32의 GPIO 하나를 제어하는 ​​방법을 보여주었습니다. 이 방법을 사용하여 더 많은 GPIO를 제어할 수 있습니다. 또한 WebSocket 프로토콜을 사용하여 언제든지 센서 판독값이나 알림을 보낼 수 있습니다.

 

이 튜토리얼이 유용했기를 바랍니다. WebSocket 프로토콜을 사용하여 더 많은 튜토리얼과 예를 만들 예정입니다. 그러니 계속 지켜봐 주세요.

 

리소스를 통해 ESP32에 대해 자세히 알아보세요:

 

  • Arduino IDE로 ESP32 배우기
  • ESP32 및 ESP8266을 사용한 MicroPython 프로그래밍
  • 더 많은 ESP32 프로젝트 및 가이드…

 

읽어주셔서 감사합니다. 배움을 멈추지 마세요. 절대로! 

 

 

반응형