Lập trình ESP32 Websocket điều khiển đèn Real time

Esp32 websocket webserver

ESP32 Websocket được sử dụng rất nhiều trong các ứng dụng Realtime, giúp các client có thể nhận được data một cách gần như tức thời. Phù hợp với nhiều ứng dụng khác nhau. Nhất là các ứng dụng điều khiển Smarthome, ……

Bài 3 Networking trong serie Lập trình ESP32 từ A tới Z

Websocket là gì?

WebSocket là một giao thức giao tiếp máy tính, hỗ trợ các channel giao tiếp full-duplex qua một kết nối TCP. Giao thức WebSocket được IETF chuẩn hóa RFC 6455 vào năm 2011. Hiện nay, API WebSocket trong Web IDL cũng đang được chuẩn hóa bởi W3C.

Sử dụng WebSockets, bạn có thể tạo một ứng dụng real-time đúng nghĩa như ứng dụng chat, phối hợp soạn thảo văn bản, giao dịch chứng khoán hay game online nhiều người chơi cùng lúc.

Trong ví dụ này chúng ta sẽ sử dụng websocket điều khiển led từ nhiều nguồn khác nhau: web browser, điện thoại, các máy tính khác nhau…

Lập trình ESP32 Websocket server

Chuẩn bị phần cứng

Chuẩn bị:

  • ESP32 development board
  • 2x 5mm LED
  • 2x 330 Ohm trở
  • Breadboard
  • Dây cắm

Sơ đồ nguyên lý, tương tự 2 bài trước là ESP32 Webserver Station Mode và ESP32 Webserver AP Mode

esp32 webserver che do wifi station

Tạo một trang web sử dụng HTML và CSS

Chúng ta sẽ sử dụng các thẻ <head>, <body> để tạo đầu và thân trang.

Trong nội dung trang, thêm thẻ <style>  để cấu hình màu sắc và kích thước các đối tượng sử dụng CSS

esp32 webserver html

Thêm thẻ <script> để viết chương trình tạo websocket và xử lý

Chi tiết trong phần phía dưới

Tạo kết nối WebSocket viết bằng js

Để kết nối đến remote host, ta tạo một object WebSocket mới và truyền vào URL của endpoint đích.

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

Handshake

Khi tạo một kết nối WebSocket, bước đầu tiên là một handshake thông qua TCP mà ở đó client và server đều đồng ý để sử dụng giao thức WebSocket.

Kết nối WebSocket được khởi tạo bằng việc nâng cấp từ giao thức HTTP sang giao thức WebSocket trong quá trình handshake giữa client và server thông qua cùng một kết nối TCP. Header Upgrade được bao gồm trong request nhằm thông báo cho server rằng client muốn tạo lập một kết nối WebSocket.

web socket
Cách làm việc của Websocket

Một khi được tạo lập các message WebSocket có thể được truyền đến và đi thông qua các method của WebSocket.

WebSocket Events

Bản chất bất đồng bộ của WebSocket có nghĩa là một khi một kết nối WebSocket được mở, ứng dụng sẽ lắng nghe những sự kiện. Để bắt đầu lắng nghe các sự kiện, bạn có thể thêm hàm callback vào object WebSocket hoặc sử dụng DOM method addEventListener() để thêm event listener.

Object WebSocket phát đi 4 sự kiện:

Open: Server phản hồi lại request để mở kết nối WebSocket. Event này thông báo rằng handshake thành công và kết nối Websocket được khởi tạo. Callback của sự kiện này là onopen.

Trong event này chúng ta chỉ in ra log: Connetion opened

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

Message: Server nhận được data chứa trong message từ client. Callback tương ứng của sự kiện này là onmessage.

Mỗi khi nhấn nút trên Browser nghĩa là Client đang gửi 1 message tới Server, sau khi sử lý dữ liệu trên Server, hàm này sẽ thay đổi Text Led1States và Led2States tương ứng với ON, OFF.

function onMessage(event) {
    var ledstate1;
    var ledstate2;
    if (event.data == "00"){
      ledstate1 = "OFF";
      ledstate2 = "OFF";
    }
    else if(event.data == "01"){
      ledstate1 = "OFF";
      ledstate2 = "ON";
    }
    else if(event.data == "10"){
      ledstate1 = "ON";
      ledstate2 = "OFF";
    }
    else if(event.data == "11"){
      ledstate1 = "ON";
      ledstate2 = "ON";
    }
    document.getElementById('state1').innerHTML = ledstate1;
    document.getElementById('state2').innerHTML = ledstate2;
  }

Error: Sự kiện xảy ra khi có bất kỳ lỗi nào trong kết nối WebSocket. Callback với sự kiện này là onerror.

Trong bài, mình không sử dụng sự kiện này

Load: Sự kiện xảy ra khi khởi tạo websocket, Callback với sự kiện này là onload()

Khi khởi tạo chúng ta sẽ khởi tạo websocket và nút nhấn trên web

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

Close: Kết nối được đóng lại. Callback tương ứng với sự kiện này là onclose.

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

Các methods (phương thức) của WebSocket

WebSocket cung cấp 2 method:

send(): method send(data) truyền data trên kết nối. Nếu vì lý do nào đó, kết nối không tồn tại hoặc bị đóng, method này sẽ trả về exception về tình trạng không hợp lệ của kết nối.

websocket.send('toggle1');

close(): Method close() dùng để đóng kết nối hiện tại. Nếu kết nối đã bị đóng từ trước đó (có thể do lỗi kết nối), nó không gây ảnh hưởng gì. Method này nhận hai argument tùy chọn: code (dạng số) và reason (dạng text)

Ngoài ra trong bài này chúng ta còn sử dụng các hàm khởi tạo và thay đổi giái trị ON,OFF cho Led

function initButton() {
  document.getElementById('button1').addEventListener('click', toggle1);
  document.getElementById('button2').addEventListener('click', toggle2);
}
function toggle1(){
  websocket.send('toggle1');
}
function toggle2(){
  websocket.send('toggle2');
}

Xử lý thông tin từ Client gửi lên Server

Trong code ESP32 chúng ta sẽ tạo ra các hàm Handle (xử lý) các sự kiện mà Client tạo ra.

Đầu tiên chúng ta sẽ khởi tạo các giá trị đèn led, wifi và include các thư viện.
Cài đặt các thư viện:

Chi tiết cách cài đặt thì các bạn đọc bài: Cài đặt platform io nhé!

esp32 webserver include

Trong Setup chúng ta sẽ khởi tạo các chân led, kết nối tới wifi

esp32 webserver setup

Tiếp tới, khởi tạo webserver websocket. Sử dụng trang index_html đã code bên trên và hàm processer để xử lý dữ liệu

esp32 webserver setup 2

Trong loop chúng ta làm mới các client và ghi giá trị ra led 1 và 2.

esp32 webserver loop

Một số hàm tự tạo để xử lý như.

notifyClients: trả về giá trị của led và gửi về các client
handleWebSocketMessage: Xử lý message của client, nếu có lệnh toggle1 và toggle 2 sẽ đảo trạng thái led và dùng notifyClients để gửi về client trạng thái led.
esp32 webserver mes
Hàm onEvent để xử lý các sự kiện khi kết nối websocket
esp32 webserver even
Hàm initWebSocket để khởi tạo
Hàm processor để sửa giá trị của STATES1 và STATES 2 trên web
esp32 webserver processor

Full code

 

#include <Arduino.h>

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

// Replace with your network credentials
const char* ssid = "Nha Tao";
const char* password = "25251325";

bool led1State = 0;
bool led2State = 0;
const int led1Pin = 26;
const int led2Pin = 27;

// 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 Websocket Web Server Websocket</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
<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>Khue Nguyen Creator</h2>
      <p class="state1">LED1: <span id="state1">%STATE1%</span></p>
      <p><button id="button1" class="button1">BUTTON1</button></p>
       <p class="state1">LED2: <span id="state2">%STATE2%</span></p>
      <p><button id="button2" class="button2">BUTTON2</button></p>
    </div>
  </div>
</body>
</html>
)rawliteral";

void notifyClients() {
  ws.textAll(String(led1State)+String(led2State));
}


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, "toggle1") == 0) {
      led1State = !led1State;
      notifyClients();
    }
    if (strcmp((char*)data, "toggle2") == 0) {
      led2State = !led2State;
      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 == "STATE1"){
    if (led1State){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
  if(var == "STATE2"){
    if (led2State){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
}

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

  pinMode(led1Pin, OUTPUT);
  digitalWrite(led1Pin, LOW);
  pinMode(led2Pin, OUTPUT);
  digitalWrite(led2Pin, 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(led1Pin, led1State);
  digitalWrite(led2Pin, led2State);
}

 

Kết quả

Sau khi build và nạp vào board chúng ta sẽ được kết quả như sau:

Kết

ESP32 Websocket được sử dụng rất nhiều trong các ứng dụng Realtime. Thay vì cần reload lại trình duyệt, server sẽ trả về các giá trị cập nhật dữ liệu tới tất cả các client đang kết nối một cách tức thời.
Nếu bạn thấy bài viết này có ích hãy để lại bình luận và đừng quên ra nhập Hội Anh Em Nghiện Lập trình nhé.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *