ESP8266 и Arduino IDE. Часть 9: WebSocket
WebSocket – это способ связи между сервером и клиентом (сервером и веб-страницей), при котором обе стороны могут свободно обмениваться данными и любая сторона может отправить данные в любое время (асинхронно) без запроса.
При использовании обычного HTTP/AJAX каждый раз создается новое соединение. WebSocket-соединение поддерживается открытым, что позволяет осуществлять двустороннюю связь в реальном времени. WebSocket является частью спецификации HTML5 и поддерживается всеми современными браузерами через JavaScript API.
Библиотека arduinoWebSockets
Для работы с WebSocket на ESP8266 используется библиотека arduinoWebSockets от Markus Sattler (Links2004). Она доступна через Arduino Library Manager или на GitHub.
Основная структура кода
Базовые шаги настройки WebSocket:
Подключить библиотеку WebSockets
Создать экземпляр WebSocketsServer на выбранном порту (в примерах используется порт 81)
Запустить сервер и привязать обработчик событий в
setup()Вызывать
webSocket.loop()в основном циклеРеализовать функцию обратного вызова
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();
}
}
Пример 2: Двусторонняя связь с локальной кнопкой
Этот пример добавляет физическую кнопку к ESP8266 (пин D6) для локального управления светодиодом. Ключевая особенность – двунаправленная связь: когда локальная кнопка изменяет состояние светодиода, веб-страница автоматически обновляется через WebSocket.
Функция проверки кнопки с подавлением дребезга
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';
}
}
Важные замечания по реализации
Время подключения: WebSocket-соединение должно быть установлено перед отправкой сообщений. Дождитесь события WStype_CONNECTED (сервер) или события „open“ сокета (клиент).
Проброс портов: WebSocket использует отдельный порт от HTTP. Порт 80 обслуживает веб-страницы; порт 81 (в примерах) обрабатывает WebSocket-трафик. Оба порта должны быть проброшены для внешнего доступа.
Множественные клиенты: Библиотека поддерживает максимум 5 одновременных подключений.
Обнаружение потери соединения: Ненадежные WiFi-отключения требуют периодических heartbeat-сообщений (AYT/»Are You There» и YIA/»Yes I Am») вместо автоматического обнаружения.
Дополнительные ресурсы
MDN Web Docs: WebSocket API
Tutorialspoint: HTML5 WebSockets
Linode: Introduction to WebSockets
GitHub: документация библиотеки arduinoWebSockets