ESP8266 и Arduino IDE. Часть 9: WebSocket

WebSocket – это способ связи между сервером и клиентом (сервером и веб-страницей), при котором обе стороны могут свободно обмениваться данными и любая сторона может отправить данные в любое время (асинхронно) без запроса.

При использовании обычного HTTP/AJAX каждый раз создается новое соединение. WebSocket-соединение поддерживается открытым, что позволяет осуществлять двустороннюю связь в реальном времени. WebSocket является частью спецификации HTML5 и поддерживается всеми современными браузерами через JavaScript API.

ESP8266 WebSocket

Библиотека arduinoWebSockets

Для работы с WebSocket на ESP8266 используется библиотека arduinoWebSockets от Markus Sattler (Links2004). Она доступна через Arduino Library Manager или на GitHub.

Библиотека WebSocket в Arduino IDE

Основная структура кода

Базовые шаги настройки WebSocket:

  1. Подключить библиотеку WebSockets

  2. Создать экземпляр WebSocketsServer на выбранном порту (в примерах используется порт 81)

  3. Запустить сервер и привязать обработчик событий в setup()

  4. Вызывать webSocket.loop() в основном цикле

  5. Реализовать функцию обратного вызова webSocketEvent()

#include <WebSocketsServer.h>

WebSocketsServer webSocket = WebSocketsServer(81);

void setup() {
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
}

void loop() {
  webSocket.loop();
}

void webSocketEvent(byte num, WStype_t type, uint8_t * payload, size_t length) {
  if(type == WStype_TEXT) {
    // Обработка текстовых данных
  }
}

Типы событий WebSocket

  • 0: WStype_ERROR

  • 1: WStype_DISCONNECTED

  • 2: WStype_CONNECTED

  • 3: WStype_TEXT

  • 4: WStype_BIN (бинарные данные)

  • 5-8: Типы фрагментов

  • 9: WStype_PING

  • 10: WStype_PONG

Параметры функции обратного вызова:

  • num – ID подключенного клиента (максимум 5 одновременных подключений)

  • type – тип события

  • payload – указатель на полученные данные (не char-массив)

  • length – размер данных


Пример 1: Управление светодиодом

Схема подключения

Светодиод с резистором подключен к пину D3 модуля ESP8266.

Схема подключения светодиода

HTML/JavaScript код

<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-size:120%;}
    #main { display: table; width: 300px; margin: auto; padding: 10px; border: 3px solid blue; border-radius: 10px; text-align:center;}
    .button { width:200px; height:40px; font-size: 110%;  }
  </style>
</head>
<body>
  <div id='main'>
    <h3>LED CONTROL</h3>
    <p id='LED_status'>LED is off</p>
    <button id='BTN_LED' class="button">Turn on the LED</button>
  </div>

<script>
  var Socket;
  function init() {
    Socket = new WebSocket('ws://' + window.location.hostname + ':81/');
  }

  document.getElementById('BTN_LED').addEventListener('click', buttonClicked);

  function buttonClicked() {
    var btn = document.getElementById('BTN_LED');
    var btnText = btn.textContent || btn.innerText;
    if (btnText === 'Turn on the LED') {
      btn.innerHTML = "Turn off the LED";
      document.getElementById('LED_status').innerHTML = 'LED is on';
      sendText('1');
    } else {
      btn.innerHTML = "Turn on the LED";
      document.getElementById('LED_status').innerHTML = 'LED is off';
      sendText('0');
    }
  }

  function sendText(data) {
    Socket.send(data);
  }

  window.onload = function(e) {
    init();
  }
</script>
</html>

Скетч Arduino – управление светодиодом

#include <ESP8266WiFi.h>
#include <WebSocketsServer.h>

WiFiServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

byte pin_led = D3;

char ssid[] = "ssid";
char pass[] = "pass";

void setup() {
  pinMode(pin_led, OUTPUT);
  digitalWrite(pin_led, LOW);

  Serial.begin(115200);
  Serial.println("Serial started at 115200");

  Serial.print(F("Connecting to "));
  Serial.println(ssid);
  WiFi.begin(ssid, pass);

  int count = 0;
  while ((WiFi.status() != WL_CONNECTED) && count < 17) {
    Serial.print(".");
    delay(500);
    count++;
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("");
    Serial.print("Failed to connect to ");
    Serial.println(ssid);
    while(1);
  }

  Serial.println("");
  Serial.println(F("[CONNECTED]"));
  Serial.print("[IP ");
  Serial.print(WiFi.localIP());
  Serial.println("]");

  server.begin();
  Serial.println("Server started");

  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
}

void loop() {
  webSocket.loop();

  WiFiClient client = server.available();
  if (!client) { return; }

  client.flush();
  client.print(header);
  client.print(html_1);
  Serial.println("New page served");

  delay(5);
}

void webSocketEvent(byte num, WStype_t type, uint8_t * payload, size_t length) {
  if(type == WStype_TEXT) {
    if (payload[0] == '0') {
      digitalWrite(pin_led, LOW);
      Serial.println("LED=off");
    }
    else if (payload[0] == '1') {
      digitalWrite(pin_led, HIGH);
      Serial.println("LED=on");
    }
  }
  else {
    Serial.print("WStype = ");
    Serial.println(type);
    Serial.print("WS payload = ");
    for(int i = 0; i < length; i++) {
      Serial.print((char) payload[i]);
    }
    Serial.println();
  }
}

Монитор порта

Веб-страница управления светодиодом

Светодиод выключен

Светодиод включен

Монитор порта с данными WebSocket

Пример 2: Двусторонняя связь с локальной кнопкой

Этот пример добавляет физическую кнопку к ESP8266 (пин D6) для локального управления светодиодом. Ключевая особенность – двунаправленная связь: когда локальная кнопка изменяет состояние светодиода, веб-страница автоматически обновляется через WebSocket.

Схема с кнопкой

Схема подключения пример 2

Демонстрация двусторонней связи

Функция проверки кнопки с подавлением дребезга

void checkSwitch() {
  newSwitchState1 = digitalRead(pin_switch);
  delay(1);
  newSwitchState2 = digitalRead(pin_switch);
  delay(1);
  newSwitchState3 = digitalRead(pin_switch);

  // Подавление дребезга: все 3 считывания должны совпадать
  if ((newSwitchState1 == newSwitchState2) && (newSwitchState1 == newSwitchState3)) {
    if (newSwitchState1 != oldSwitchState) {
      if (newSwitchState1 == HIGH) {
        LEDstatus = !LEDstatus;
        if (LEDstatus == HIGH) {
          digitalWrite(pin_led, HIGH);
          webSocket.broadcastTXT("1");
          Serial.println("LED is ON");
        }
        else {
          digitalWrite(pin_led, LOW);
          webSocket.broadcastTXT("0");
          Serial.println("LED is OFF");
        }
      }
      oldSwitchState = newSwitchState1;
    }
  }
}

Ключевая функция: webSocket.broadcastTXT() отправляет данные всем подключенным клиентам.

JavaScript: обработка входящих сообщений

function init() {
  Socket = new WebSocket('ws://' + window.location.hostname + ':81/');
  Socket.onmessage = function(event) { processReceivedCommand(event); };
}

function processReceivedCommand(evt) {
  document.getElementById('rd').innerHTML = evt.data;
  if (evt.data === '0') {
    document.getElementById('BTN_LED').innerHTML = 'Turn on the LED';
    document.getElementById('LED_status').innerHTML = 'LED is off';
  }
  if (evt.data === '1') {
    document.getElementById('BTN_LED').innerHTML = 'Turn off the LED';
    document.getElementById('LED_status').innerHTML = 'LED is on';
  }
}

Важные замечания по реализации

  1. Время подключения: WebSocket-соединение должно быть установлено перед отправкой сообщений. Дождитесь события WStype_CONNECTED (сервер) или события „open“ сокета (клиент).

  2. Проброс портов: WebSocket использует отдельный порт от HTTP. Порт 80 обслуживает веб-страницы; порт 81 (в примерах) обрабатывает WebSocket-трафик. Оба порта должны быть проброшены для внешнего доступа.

  3. Множественные клиенты: Библиотека поддерживает максимум 5 одновременных подключений.

  4. Обнаружение потери соединения: Ненадежные WiFi-отключения требуют периодических heartbeat-сообщений (AYT/»Are You There» и YIA/»Yes I Am») вместо автоматического обнаружения.


Дополнительные ресурсы

  • MDN Web Docs: WebSocket API

  • Tutorialspoint: HTML5 WebSockets

  • Linode: Introduction to WebSockets

  • GitHub: документация библиотеки arduinoWebSockets