152 lines
4.3 KiB
C++
152 lines
4.3 KiB
C++
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright (C) 2023-2024 Mathieu Carbou
|
|
*/
|
|
#include "MycilaWebSerial.h"
|
|
|
|
#include "MycilaWebSerialPage.h"
|
|
|
|
void WebSerialClass::setAuthentication(const String& username, const String& password) {
|
|
_username = username;
|
|
_password = password;
|
|
_authenticate = !_username.isEmpty() && !_password.isEmpty();
|
|
if (_ws) {
|
|
_ws->setAuthentication(_username.c_str(), _password.c_str());
|
|
}
|
|
}
|
|
|
|
void WebSerialClass::begin(AsyncWebServer* server, const char* url) {
|
|
_server = server;
|
|
|
|
String backendUrl = url;
|
|
backendUrl.concat("ws");
|
|
_ws = new AsyncWebSocket(backendUrl);
|
|
|
|
if (_authenticate) {
|
|
_ws->setAuthentication(_username.c_str(), _password.c_str());
|
|
}
|
|
|
|
_server->on(url, HTTP_GET, [&](AsyncWebServerRequest* request) {
|
|
if (_authenticate) {
|
|
if (!request->authenticate(_username.c_str(), _password.c_str()))
|
|
return request->requestAuthentication();
|
|
}
|
|
AsyncWebServerResponse* response = request->beginResponse(200, "text/html", WEBSERIAL_HTML, sizeof(WEBSERIAL_HTML));
|
|
response->addHeader("Content-Encoding", "gzip");
|
|
request->send(response);
|
|
});
|
|
|
|
_ws->onEvent([&](__unused AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, __unused void* arg, uint8_t* data, __unused size_t len) -> void {
|
|
if (type == WS_EVT_CONNECT) {
|
|
client->setCloseClientOnQueueFull(false);
|
|
return;
|
|
}
|
|
if (type == WS_EVT_DATA) {
|
|
AwsFrameInfo* info = (AwsFrameInfo*)arg;
|
|
if (info->final && info->index == 0 && info->len == len) {
|
|
if (info->opcode == WS_TEXT) {
|
|
data[len] = 0;
|
|
}
|
|
if (strcmp((char*)data, "ping") == 0)
|
|
client->text("pong");
|
|
else if (_recv)
|
|
_recv(data, len);
|
|
}
|
|
}
|
|
});
|
|
|
|
_server->addHandler(_ws);
|
|
}
|
|
|
|
void WebSerialClass::onMessage(WSLMessageHandler recv) {
|
|
_recv = recv;
|
|
}
|
|
|
|
void WebSerialClass::onMessage(WSLStringMessageHandler callback) {
|
|
_recvString = callback;
|
|
_recv = [&](uint8_t* data, size_t len) {
|
|
if (data && len) {
|
|
String msg;
|
|
msg.reserve(len);
|
|
msg.concat((char*)data);
|
|
_recvString(msg);
|
|
}
|
|
};
|
|
}
|
|
|
|
size_t WebSerialClass::write(uint8_t m) {
|
|
if (!_ws)
|
|
return 0;
|
|
|
|
// We do not support non-buffered write on webserial for the HIGH_PERF version
|
|
// we fail with a stack trace allowing the user to change the code to use write(const uint8_t* buffer, size_t size) instead
|
|
if (!_initialBufferCapacity) {
|
|
#ifdef ESP8266
|
|
ets_printf("'-D WSL_FAIL_ON_NON_BUFFERED_WRITE' is set: non-buffered write is not supported. Please use write(const uint8_t* buffer, size_t size) instead.");
|
|
#else
|
|
log_e("'-D WSL_FAIL_ON_NON_BUFFERED_WRITE' is set: non-buffered write is not supported. Please use write(const uint8_t* buffer, size_t size) instead.");
|
|
#endif
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
write(&m, 1);
|
|
return (1);
|
|
}
|
|
|
|
size_t WebSerialClass::write(const uint8_t* buffer, size_t size) {
|
|
if (!_ws || size == 0)
|
|
return 0;
|
|
|
|
// No buffer, send directly (i.e. use case for log streaming)
|
|
if (!_initialBufferCapacity) {
|
|
size = buffer[size - 1] == '\n' ? size - 1 : size;
|
|
_send(buffer, size);
|
|
return size;
|
|
}
|
|
|
|
// fill the buffer while sending data for each EOL
|
|
size_t start = 0, end = 0;
|
|
while (end < size) {
|
|
if (buffer[end] == '\n') {
|
|
if (end > start) {
|
|
_buffer.concat(reinterpret_cast<const char*>(buffer + start), end - start);
|
|
}
|
|
_send(reinterpret_cast<const uint8_t*>(_buffer.c_str()), _buffer.length());
|
|
start = end + 1;
|
|
}
|
|
end++;
|
|
}
|
|
if (end > start) {
|
|
_buffer.concat(reinterpret_cast<const char*>(buffer + start), end - start);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
void WebSerialClass::_send(const uint8_t* buffer, size_t size) {
|
|
if (_ws && size > 0) {
|
|
_ws->cleanupClients(WSL_MAX_WS_CLIENTS);
|
|
if (_ws->count()) {
|
|
_ws->textAll((const char*)buffer, size);
|
|
}
|
|
}
|
|
|
|
// if buffer grew too much, free it, otherwise clear it
|
|
if (_initialBufferCapacity) {
|
|
if (_buffer.length() > _initialBufferCapacity) {
|
|
setBuffer(_initialBufferCapacity);
|
|
} else {
|
|
_buffer.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebSerialClass::setBuffer(size_t initialCapacity) {
|
|
assert(initialCapacity <= UINT16_MAX);
|
|
_initialBufferCapacity = initialCapacity;
|
|
_buffer = String();
|
|
_buffer.reserve(initialCapacity);
|
|
}
|
|
|
|
WebSerialClass WebSerial;
|