본문 바로가기

ESP32

ESP32 WebSocket 서버와 제어 출력

반응형

 

 

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 프로젝트 및 가이드…

 

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

 

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩