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 웹 서버 모든 클라이언트 업데이트 작동 방식
"토글" 버튼을 클릭하면 다음과 같은 일이 발생합니다.
- "토글" 버튼을 클릭합니다.
- 클라이언트(브라우저)가 "토글" 메시지와 함께 WebSocket 프로토콜을 통해 데이터를 보냅니다.
- ESP32(서버)가 이 메시지를 수신하여 LED 상태를 토글해야 한다는 것을 알고 있습니다. 이전에 LED가 꺼져 있었다면 켭니다.
- 그런 다음 WebSocket 프로토콜을 통해 모든 클라이언트에 새 LED 상태와 함께 데이터를 보냅니다.
- 클라이언트가 메시지를 수신하고 웹 페이지에서 LED 상태를 적절히 업데이트합니다. 이렇게 하면 변경 사항이 발생할 때 모든 클라이언트를 거의 즉시 업데이트할 수 있습니다.
Arduino IDE 준비
Arduino IDE를 사용하여 ESP32 보드를 프로그래밍하므로 Arduino IDE에 설치되어 있는지 확인하십시오.
Arduino IDE(Windows, Mac OS X, Linux)에 ESP32 보드 설치
라이브러리 설치 - 비동기 웹 서버
웹 서버를 빌드하려면 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 프로젝트 및 가이드…
읽어주셔서 감사합니다. 배움을 멈추지 마세요. 절대로!