Skip to content

Commit bfbb342

Browse files
committed
Added an easy to use WebSocket handler AsyncWebSocketMessageHandler with an example
1 parent 25845e7 commit bfbb342

File tree

3 files changed

+190
-4
lines changed

3 files changed

+190
-4
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
3+
4+
//
5+
// WebSocket example using the easy to use AsyncWebSocketMessageHandler handler that only supports unfragmented messages
6+
//
7+
8+
#include <Arduino.h>
9+
#ifdef ESP32
10+
#include <AsyncTCP.h>
11+
#include <WiFi.h>
12+
#elif defined(ESP8266)
13+
#include <ESP8266WiFi.h>
14+
#include <ESPAsyncTCP.h>
15+
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
16+
#include <RPAsyncTCP.h>
17+
#include <WiFi.h>
18+
#endif
19+
20+
#include <ESPAsyncWebServer.h>
21+
22+
static AsyncWebServer server(80);
23+
24+
// create an easy-to-use handler
25+
static AsyncWebSocketMessageHandler wsHandler;
26+
27+
// add it to the websocket server
28+
static AsyncWebSocket ws("/ws", wsHandler.eventHandler());
29+
30+
// alternatively you can do as usual:
31+
//
32+
// static AsyncWebSocket ws("/ws");
33+
// ws.onEvent(wsHandler.eventHandler());
34+
35+
static const char *htmlContent PROGMEM = R"(
36+
<!DOCTYPE html>
37+
<html>
38+
<head>
39+
<title>WebSocket</title>
40+
</head>
41+
<body>
42+
<h1>WebSocket Example</h1>
43+
<>Open your browser console!</p>
44+
<input type="text" id="message" placeholder="Type a message">
45+
<button onclick='sendMessage()'>Send</button>
46+
<script>
47+
var ws = new WebSocket('ws://192.168.4.1/ws');
48+
ws.onopen = function() {
49+
console.log("WebSocket connected");
50+
};
51+
ws.onmessage = function(event) {
52+
console.log("WebSocket message: " + event.data);
53+
};
54+
ws.onclose = function() {
55+
console.log("WebSocket closed");
56+
};
57+
ws.onerror = function(error) {
58+
console.log("WebSocket error: " + error);
59+
};
60+
function sendMessage() {
61+
var message = document.getElementById("message").value;
62+
ws.send(message);
63+
console.log("WebSocket sent: " + message);
64+
}
65+
</script>
66+
</body>
67+
</html>
68+
)";
69+
static const size_t htmlContentLength = strlen_P(htmlContent);
70+
71+
void setup() {
72+
Serial.begin(115200);
73+
74+
#ifndef CONFIG_IDF_TARGET_ESP32H2
75+
WiFi.mode(WIFI_AP);
76+
WiFi.softAP("esp-captive");
77+
#endif
78+
79+
// serves root html page
80+
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
81+
request->send(200, "text/html", (const uint8_t *)htmlContent, htmlContentLength);
82+
});
83+
84+
wsHandler.onConnect([](AsyncWebSocket *server, AsyncWebSocketClient *client) {
85+
Serial.printf("Client %" PRIu32 " connected\n", client->id());
86+
server->textAll("New client: " + String(client->id()));
87+
});
88+
89+
wsHandler.onDisconnect([](AsyncWebSocket *server, uint32_t clientId) {
90+
Serial.printf("Client %" PRIu32 " disconnected\n", clientId);
91+
server->textAll("Client " + String(clientId) + " disconnected");
92+
});
93+
94+
wsHandler.onError([](AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len) {
95+
Serial.printf("Client %" PRIu32 " error: %" PRIu16 ": %s\n", client->id(), errorCode, reason);
96+
});
97+
98+
wsHandler.onData([](AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len) {
99+
Serial.printf("Client %" PRIu32 " data: %s\n", client->id(), (const char *)data);
100+
});
101+
102+
server.addHandler(&ws);
103+
server.begin();
104+
}
105+
106+
static uint32_t lastWS = 0;
107+
static uint32_t deltaWS = 2000;
108+
109+
void loop() {
110+
uint32_t now = millis();
111+
112+
if (now - lastWS >= deltaWS) {
113+
ws.cleanupClients();
114+
ws.printfAll("now: %" PRIu32 "\n", now);
115+
lastWS = millis();
116+
#ifdef ESP32
117+
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
118+
#endif
119+
}
120+
}

platformio.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ src_dir = examples/PerfTests
3232
; src_dir = examples/Templates
3333
; src_dir = examples/Upload
3434
; src_dir = examples/WebSocket
35+
; src_dir = examples/WebSocketEasy
3536

3637
[env]
3738
framework = arduino

src/AsyncWebSocket.h

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ class AsyncWebSocket : public AsyncWebHandler {
291291
String _url;
292292
std::list<AsyncWebSocketClient> _clients;
293293
uint32_t _cNextId;
294-
AwsEventHandler _eventHandler{nullptr};
294+
AwsEventHandler _eventHandler;
295295
AwsHandshakeHandler _handshakeHandler;
296296
bool _enabled;
297297
#ifdef ESP32
@@ -305,9 +305,9 @@ class AsyncWebSocket : public AsyncWebHandler {
305305
PARTIALLY_ENQUEUED = 2,
306306
} SendStatus;
307307

308-
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
309-
AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
310-
~AsyncWebSocket(){};
308+
explicit AsyncWebSocket(const char *url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
309+
AsyncWebSocket(const String &url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
310+
~AsyncWebSocket() {};
311311
const char *url() const {
312312
return _url.c_str();
313313
}
@@ -413,4 +413,69 @@ class AsyncWebSocketResponse : public AsyncWebServerResponse {
413413
}
414414
};
415415

416+
class AsyncWebSocketMessageHandler {
417+
public:
418+
AwsEventHandler eventHandler() const {
419+
return _handler;
420+
}
421+
422+
void onConnect(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> onConnect) {
423+
_onConnect = onConnect;
424+
}
425+
426+
void onDisconnect(std::function<void(AsyncWebSocket *server, uint32_t clientId)> onDisconnect) {
427+
_onDisconnect = onDisconnect;
428+
}
429+
430+
/**
431+
* Error callback
432+
* @param reason null-terminated string
433+
* @param len length of the string
434+
*/
435+
void onError(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> onError) {
436+
_onError = onError;
437+
}
438+
439+
/**
440+
* Data callback
441+
* @param data pointer to the data (binary or char), will be null-terminated. This handler expects the user to know which data type he uses.
442+
*/
443+
void onData(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> onData) {
444+
_onData = onData;
445+
}
446+
447+
private:
448+
// clang-format off
449+
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> _onConnect;
450+
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> _onError;
451+
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> _onData;
452+
std::function<void(AsyncWebSocket *server, uint32_t clientId)> _onDisconnect;
453+
// clang-format on
454+
455+
// this handler is meant to only support 1-frame messages (== unfragmented messages)
456+
AwsEventHandler _handler = [this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
457+
if (type == WS_EVT_CONNECT) {
458+
if (_onConnect) {
459+
_onConnect(server, client);
460+
}
461+
} else if (type == WS_EVT_DISCONNECT) {
462+
if (_onDisconnect) {
463+
_onDisconnect(server, client->id());
464+
}
465+
} else if (type == WS_EVT_ERROR) {
466+
if (_onError) {
467+
_onError(server, client, *((uint16_t *)arg), (const char *)data, len);
468+
}
469+
} else if (type == WS_EVT_DATA) {
470+
if (_onData) {
471+
AwsFrameInfo *info = (AwsFrameInfo *)arg;
472+
if (info->final && info->index == 0 && info->len == len) {
473+
data[len] = 0;
474+
_onData(server, client, data, len);
475+
}
476+
}
477+
}
478+
};
479+
};
480+
416481
#endif /* ASYNCWEBSOCKET_H_ */

0 commit comments

Comments
 (0)