Skip to content

Commit 8d712ea

Browse files
Merge pull request #144 from ESP32Async/ws
Added an easy to use WebSocket handler AsyncWebSocketMessageHandler with an example
2 parents 0c20c5a + 8a0d2dd commit 8d712ea

File tree

3 files changed

+210
-3
lines changed

3 files changed

+210
-3
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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.onMessage([](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+
wsHandler.onFragment([](AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len) {
103+
Serial.printf("Client %" PRIu32 " fragment %" PRIu32 ": %s\n", client->id(), frameInfo->num, (const char *)data);
104+
});
105+
106+
server.addHandler(&ws);
107+
server.begin();
108+
}
109+
110+
static uint32_t lastWS = 0;
111+
static uint32_t deltaWS = 2000;
112+
113+
void loop() {
114+
uint32_t now = millis();
115+
116+
if (now - lastWS >= deltaWS) {
117+
ws.cleanupClients();
118+
ws.printfAll("now: %" PRIu32 "\n", now);
119+
lastWS = millis();
120+
#ifdef ESP32
121+
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
122+
#endif
123+
}
124+
}

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: 85 additions & 3 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,8 +305,8 @@ 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) {}
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) {}
310310
~AsyncWebSocket(){};
311311
const char *url() const {
312312
return _url.c_str();
@@ -413,4 +413,86 @@ 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+
* Complete message callback
441+
* @param data pointer to the data (binary or null-terminated string). This handler expects the user to know which data type he uses.
442+
*/
443+
void onMessage(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> onMessage) {
444+
_onMessage = onMessage;
445+
}
446+
447+
/**
448+
* Fragmented message callback
449+
* @param data pointer to the data (binary or null-terminated string), will be null-terminated. This handler expects the user to know which data type he uses.
450+
*/
451+
// clang-format off
452+
void onFragment(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> onFragment) {
453+
_onFragment = onFragment;
454+
}
455+
// clang-format on
456+
457+
private:
458+
// clang-format off
459+
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> _onConnect;
460+
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> _onError;
461+
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> _onMessage;
462+
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> _onFragment;
463+
std::function<void(AsyncWebSocket *server, uint32_t clientId)> _onDisconnect;
464+
// clang-format on
465+
466+
// this handler is meant to only support 1-frame messages (== unfragmented messages)
467+
AwsEventHandler _handler = [this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
468+
if (type == WS_EVT_CONNECT) {
469+
if (_onConnect) {
470+
_onConnect(server, client);
471+
}
472+
} else if (type == WS_EVT_DISCONNECT) {
473+
if (_onDisconnect) {
474+
_onDisconnect(server, client->id());
475+
}
476+
} else if (type == WS_EVT_ERROR) {
477+
if (_onError) {
478+
_onError(server, client, *((uint16_t *)arg), (const char *)data, len);
479+
}
480+
} else if (type == WS_EVT_DATA) {
481+
AwsFrameInfo *info = (AwsFrameInfo *)arg;
482+
if (info->opcode == WS_TEXT) {
483+
data[len] = 0;
484+
}
485+
if (info->final && info->index == 0 && info->len == len) {
486+
if (_onMessage) {
487+
_onMessage(server, client, data, len);
488+
}
489+
} else {
490+
if (_onFragment) {
491+
_onFragment(server, client, info, data, len);
492+
}
493+
}
494+
}
495+
};
496+
};
497+
416498
#endif /* ASYNCWEBSOCKET_H_ */

0 commit comments

Comments
 (0)