1
0
mirror of https://github.com/s00500/ESPUI.git synced 2025-07-04 01:00:19 +00:00

10 Commits
1.4.3 ... 1.4.6

Author SHA1 Message Date
b421b84b11 version bump 2018-05-13 19:50:48 +02:00
f1012b2fe2 Closes #12 implementing all events for websockets 2018-05-13 19:44:56 +02:00
cc633a7c85 Closes #11 Bugfix for retrun values on error 2018-05-13 19:39:21 +02:00
40b13430cb #9 adding control id as return value 2018-04-17 19:54:07 +02:00
dda4e9e771 Merge pull request #8 from per1234/keywords-separa
Use correct separator in keywords.txt
2018-02-20 09:19:09 +01:00
1390b73218 Use correct separator in keywords.txt
The Arduino IDE currently requires the use of a tab separator between the name and identifier. Without this tab the keyword is not highlighted.

Reference:
https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#keywords
2018-02-19 23:39:35 -08:00
5f7f8dd4e5 ixing filelist functions for esp8266 2018-01-14 12:22:26 +01:00
7e4dbd7e03 version bump fo #6 patch 2018-01-08 12:27:05 +01:00
6dabc32905 #6 adding list() debug function for fs and commenting out deleteFile operations 2018-01-08 12:26:32 +01:00
feedd21413 changing order of functions because of beta compiler 2018-01-08 12:25:36 +01:00
6 changed files with 314 additions and 191 deletions

View File

@ -51,7 +51,7 @@ Download the [Repository](https://github.com/s00500/ESPUI/archive/master.zip), G
ESPUI **NEEDS** its files burnt on the SPIFFS filesystem on the ESP. **Without this ESPUI will NOT work at all**
There are now two ways to do this: you can either use the upload tool or you use the library function `ESPUI.prepareFileSystem()`
#### Simple filesystem preparation (recomended)
#### Simple filesystem preparation (recomended, but currently not working well on esp32, see issues)
Just open the example sketch **prepareFileSystem** and run it on the ESP, (give it 5 - 10 seconds),
The library will create all needed files.
@ -81,10 +81,13 @@ Now you are set to go and use any code you want to with this library
- ~~Setup SPIFFS using values in program memory~~
- ~~ESP8266 support~~
- Document slider
- New images in docu
- proper return value (as int and not as string) for slider
- Maybe a slider range setting, meanwhile please use map()
- Improve slider stability
- Improve general stability
- Multiline Labels
- PlattformIO Integration
## Documentation

View File

@ -12,60 +12,6 @@ const char *password = "";
long oldTime = 0;
bool switchi = false;
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_AP);
#if defined(ESP32)
WiFi.setHostname(ssid);
#else
WiFi.hostname(ssid);
#endif
WiFi.softAP(ssid);
// WiFi.softAP(ssid, password);
Serial.println("");
Serial.print("IP address: ");
Serial.println(WiFi.softAPIP());
// change the beginning to this if you want to join an existing network
/*
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
*/
ESPUI.label("Status:", COLOR_TURQUOISE, "Stop");
ESPUI.label("Millis:", COLOR_EMERALD, "0");
ESPUI.button("Push Button", &buttonCallback, COLOR_PETERRIVER);
ESPUI.button("Other Button", &buttonExample, COLOR_WETASPHALT, "Press");
ESPUI.pad("Pad with center", true, &padExample, COLOR_SUNFLOWER);
ESPUI.pad("Pad without center", false, &padExample, COLOR_CARROT);
ESPUI.switcher("Switch one", false, &switchExample, COLOR_ALIZARIN);
ESPUI.switcher("Switch two", true, &otherSwitchExample, COLOR_NONE);
ESPUI.slider("Slider one", &slider, COLOR_ALIZARIN, "30");
ESPUI.slider("Slider two", &slider, COLOR_NONE, "100");
ESPUI.begin("ESP32 Control");
}
void loop(void) {
if (millis() - oldTime > 5000) {
ESPUI.print("Millis:", String(millis()));
switchi = !switchi;
ESPUI.updateSwitcher("Switch one", switchi);
oldTime = millis();
}
}
void slider(Control sender, int type) {
Serial.println(sender.value);
}
@ -155,3 +101,57 @@ void otherSwitchExample(Control sender, int value) {
Serial.print(" ");
Serial.println(sender.id);
}
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_AP);
#if defined(ESP32)
WiFi.setHostname(ssid);
#else
WiFi.hostname(ssid);
#endif
WiFi.softAP(ssid);
// WiFi.softAP(ssid, password);
Serial.println("");
Serial.print("IP address: ");
Serial.println(WiFi.softAPIP());
// change the beginning to this if you want to join an existing network
/*
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
*/
ESPUI.label("Status:", COLOR_TURQUOISE, "Stop");
ESPUI.label("Millis:", COLOR_EMERALD, "0");
ESPUI.button("Push Button", &buttonCallback, COLOR_PETERRIVER);
ESPUI.button("Other Button", &buttonExample, COLOR_WETASPHALT, "Press");
ESPUI.pad("Pad with center", true, &padExample, COLOR_SUNFLOWER);
ESPUI.pad("Pad without center", false, &padExample, COLOR_CARROT);
ESPUI.switcher("Switch one", false, &switchExample, COLOR_ALIZARIN);
ESPUI.switcher("Switch two", true, &otherSwitchExample, COLOR_NONE);
ESPUI.slider("Slider one", &slider, COLOR_ALIZARIN, "30");
ESPUI.slider("Slider two", &slider, COLOR_NONE, "100");
ESPUI.begin("ESP32 Control");
}
void loop(void) {
if (millis() - oldTime > 5000) {
ESPUI.print("Millis:", String(millis()));
switchi = !switchi;
ESPUI.updateSwitcher("Switch one", switchi);
oldTime = millis();
}
}

View File

@ -15,7 +15,7 @@ ESPUI KEYWORD1
label KEYWORD2
button KEYWORD2
switcher KEYWORD2
switcher KEYWORD2
pad KEYWORD2
slider KEYWORD2
@ -31,18 +31,18 @@ updateSwitcher KEYWORD2
# Constants (LITERAL1)
#######################################
B_DOWN LITERAL1
B_UP LITERAL1
P_LEFT_DOWN LITERAL1
P_LEFT_UP LITERAL1
P_RIGHT_DOWN LITERAL1
P_RIGHT_UP LITERAL1
P_FOR_DOWN LITERAL1
P_FOR_UP LITERAL1
P_BACK_DOWN LITERAL1
P_BACK_UP LITERAL1
P_CENTER_DOWN LITERAL1
P_CENTER_UP LITERAL1
S_ACTIVE LITERAL1
S_INACTIVE LITERAL1
SL_VALUE LITERAL1
B_DOWN LITERAL1
B_UP LITERAL1
P_LEFT_DOWN LITERAL1
P_LEFT_UP LITERAL1
P_RIGHT_DOWN LITERAL1
P_RIGHT_UP LITERAL1
P_FOR_DOWN LITERAL1
P_FOR_UP LITERAL1
P_BACK_DOWN LITERAL1
P_BACK_UP LITERAL1
P_CENTER_DOWN LITERAL1
P_CENTER_UP LITERAL1
S_ACTIVE LITERAL1
S_INACTIVE LITERAL1
SL_VALUE LITERAL1

View File

@ -1,5 +1,5 @@
name=ESPUI
version=1.4.3
version=1.4.6
author=Lukas Bachschwell
maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
sentence=ESP32 and ESP8266 Web Interface Library

View File

@ -2,120 +2,226 @@
#include "uploadDataIndex.h"
#include "uploadDataStyle.h"
#include "uploadDataNormalize.h"
#include "uploadDataStyle.h"
#include "uploadDataControls.h"
#include "uploadDataZepto.h"
#include "uploadDataSlider.h"
#include "uploadDataZepto.h"
#include <ESPAsyncWebServer.h>
#include <functional>
// ################# Spiffs functions
void deleteFile(fs::FS &fs, const char * path){
if(!fs.exists(path)){
Serial.printf("File: %s does not exist, not deleting\n", path);
return;
}
#if defined(ESP32)
void listDir(const char *dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
Serial.printf("Deleting file: %s\n", path);
File root = SPIFFS.open(dirname);
if(fs.remove(path)){
Serial.println("File deleted");
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(file.name(), levels - 1);
}
} else {
Serial.println("Delete failed");
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
#else
void listDir(const char *dirname, uint8_t levels) {
// ignoring levels for esp8266
Serial.printf("Listing directory: %s\n", dirname);
String str = "";
Dir dir = SPIFFS.openDir("/");
while (dir.next()) {
Serial.print(" FILE: ");
Serial.print(dir.fileName());
Serial.print(" SIZE: ");
Serial.println(dir.fileSize());
}
}
void writeFile(fs::FS &fs, const char * path, const char * data){
Serial.printf("Writing file: %s\n", path);
#endif
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
if(file.print(FPSTR(data))){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
void ESPUIClass::list() {
if (!SPIFFS.begin()) {
Serial.println("SPIFFS Mount Failed");
return;
}
listDir("/", 1);
#if defined(ESP32)
Serial.println(SPIFFS.totalBytes());
Serial.println(SPIFFS.usedBytes());
#else
FSInfo fs_info;
SPIFFS.info(fs_info);
Serial.println(fs_info.totalBytes);
Serial.println(fs_info.usedBytes);
#endif
}
void deleteFile(const char *path) {
Serial.print(SPIFFS.exists(path));
if (!SPIFFS.exists(path)) {
Serial.printf("File: %s does not exist, not deleting\n", path);
return;
}
Serial.printf("Deleting file: %s\n", path);
if (SPIFFS.remove(path)) {
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void writeFile(const char *path, const char *data) {
Serial.printf("Writing file: %s\n", path);
File file = SPIFFS.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
#if defined(ESP32)
if (file.print(data)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
#else
if (file.print(FPSTR(data))) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
#endif
file.close();
}
// end Spiffs functions
void ESPUIClass::prepareFileSystem(){
// this function should only be used once
void ESPUIClass::prepareFileSystem() {
// this function should only be used once
Serial.println("About to prepare filesystem...");
Serial.println("About to prepare filesystem...");
#if defined(ESP32)
if(!SPIFFS.begin(true)) {
Serial.println("SPIFFS Mount Failed");
return;
}
#else
SPIFFS.begin();
SPIFFS.format();
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS Mount Failed");
return;
}
listDir("/", 1);
Serial.println("SPIFFS Mount ESP32 Done");
#else
SPIFFS.format();
SPIFFS.begin();
Serial.println("SPIFFS Mount ESP8266 Done");
#endif
deleteFile(SPIFFS, "/index.htm");
// TODO: This is a workaround, have to find out why SPIFFS on ESP32 behaves
// incredibly strangely, see issue #6
/*
deleteFile("/index.htm");
deleteFile(SPIFFS, "/css/style.css");
deleteFile(SPIFFS, "/css/normalize.css");
deleteFile("/css/style.css");
deleteFile("/css/normalize.css");
deleteFile(SPIFFS, "/js/controls.js");
deleteFile(SPIFFS, "/js/zepto.min.js");
deleteFile(SPIFFS, "/js/slider.js");
deleteFile("/js/zepto.min.js");
deleteFile("/js/controls.js");
deleteFile("/js/slider.js");
*/
Serial.println('Cleanup done');
Serial.println("Cleanup done");
// Now write
writeFile(SPIFFS, "/index.htm", HTML_INDEX);
// Now write
writeFile("/index.htm", HTML_INDEX);
writeFile(SPIFFS, "/css/style.css", CSS_STYLE);
writeFile(SPIFFS, "/css/normalize.css", CSS_NORMALIZE);
writeFile("/css/style.css", CSS_STYLE);
writeFile("/css/normalize.css", CSS_NORMALIZE);
writeFile(SPIFFS, "/js/zepto.min.js", JS_ZEPTO);
writeFile(SPIFFS, "/js/controls.js", JS_CONTROLS);
writeFile(SPIFFS, "/js/slider.js", JS_SLIDER);
writeFile("/js/zepto.min.js", JS_ZEPTO);
writeFile("/js/controls.js", JS_CONTROLS);
writeFile("/js/slider.js", JS_SLIDER);
Serial.println("Done Initializing filesystem :-)");
Serial.println("Done Initializing filesystem :-)");
#if defined(ESP32)
listDir("/", 1);
#endif
SPIFFS.end();
}
// Handle Websockets Communication
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
AwsEventType type, void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_DISCONNECT:
case WS_EVT_DISCONNECT: {
if (debug)
Serial.printf("Disconnected!\n");
break;
}
case WS_EVT_PONG: {
if (debug)
Serial.printf("Received PONG!\n");
break;
}
case WS_EVT_ERROR: {
if (debug)
Serial.printf("WebSocket Error!\n");
break;
}
case WS_EVT_CONNECT: {
if (debug){
if (debug) {
Serial.print("Connected: ");
Serial.println(client->id());
}
ESPUI.jsonDom(client);
if (debug){
if (debug) {
Serial.println("JSON Data Sent to Client!");
}
}
break;
case WS_EVT_DATA:
} break;
case WS_EVT_DATA: {
String msg = "";
for (size_t i = 0; i < len; i++) {
msg += (char)data[i];
}
int id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
if (id >= ESPUI.cIndex){
if(debug) Serial.println("Maleformated id in websocket message");
if (id >= ESPUI.cIndex) {
if (debug)
Serial.println("Maleformated id in websocket message");
return;
}
Control *c = ESPUI.controls[msg.substring(msg.lastIndexOf(':') + 1).toInt()];
if (msg.startsWith("bdown:")) {
@ -149,20 +255,24 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
ESPUI.updateSwitcher(c->id, false);
c->callback(*c, S_INACTIVE);
} else if (msg.startsWith("slvalue:")) {
int value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')).toInt();
int value =
msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')).toInt();
ESPUI.updateSlider(c->id, value, client->id());
c->callback(*c, SL_VALUE);
}
}
break;
default:
break;
}
}
void ESPUIClass::label(const char *label, int color, String value) {
int ESPUIClass::label(const char *label, int color, String value) {
if (labelExists(label)) {
if (debug)
Serial.println("UI ERROR: Element " + String(label) +
" exists, skipping creating element!");
return;
return -1;
}
Control *newL = new Control();
@ -177,15 +287,17 @@ void ESPUIClass::label(const char *label, int color, String value) {
newL->id = cIndex;
controls[cIndex] = newL;
cIndex++;
return cIndex - 1;
}
// TODO: this still needs a range setting
void ESPUIClass::slider(const char *label, void (*callBack)(Control, int), int color, String value) {
int ESPUIClass::slider(const char *label, void (*callBack)(Control, int),
int color, String value) {
if (labelExists(label)) {
if (debug)
Serial.println("UI ERROR: Element " + String(label) +
" exists, skipping creating element!");
return;
return -1;
}
Control *newSL = new Control();
@ -200,15 +312,16 @@ void ESPUIClass::slider(const char *label, void (*callBack)(Control, int), int c
newSL->id = cIndex;
controls[cIndex] = newSL;
cIndex++;
return cIndex - 1;
}
void ESPUIClass::button(const char *label, void (*callBack)(Control, int),
int color, String value) {
int ESPUIClass::button(const char *label, void (*callBack)(Control, int),
int color, String value) {
if (labelExists(label)) {
if (debug)
Serial.println("UI ERROR: Element " + String(label) +
" exists, skipping creating element!");
return;
return -1;
}
Control *newB = new Control();
@ -225,15 +338,16 @@ void ESPUIClass::button(const char *label, void (*callBack)(Control, int),
newB->id = cIndex;
controls[cIndex] = newB;
cIndex++;
return cIndex - 1;
}
void ESPUIClass::switcher(const char *label, bool startState,
void (*callBack)(Control, int), int color) {
int ESPUIClass::switcher(const char *label, bool startState,
void (*callBack)(Control, int), int color) {
if (labelExists(label)) {
if (debug)
Serial.println("UI ERROR: Element " + String(label) +
" exists, skipping creating element!");
return;
return -1;
}
Control *newS = new Control();
@ -245,15 +359,16 @@ void ESPUIClass::switcher(const char *label, bool startState,
newS->id = cIndex;
controls[cIndex] = newS;
cIndex++;
return cIndex - 1;
}
void ESPUIClass::pad(const char *label, bool center,
void (*callBack)(Control, int), int color) {
int ESPUIClass::pad(const char *label, bool center,
void (*callBack)(Control, int), int color) {
if (labelExists(label)) {
if (debug)
Serial.println("UI ERROR: Element " + String(label) +
" exists, skipping creating element!");
return;
return -1;
}
Control *newP = new Control();
@ -267,6 +382,7 @@ void ESPUIClass::pad(const char *label, bool center,
newP->id = cIndex;
controls[cIndex] = newP;
cIndex++;
return cIndex - 1;
}
void ESPUIClass::print(int id, String value) {
@ -296,7 +412,7 @@ void ESPUIClass::print(String label, String value) {
print(getIdByLabel(label), value);
}
void ESPUIClass::updateSwitcher(int id, bool nValue, int clientId ) {
void ESPUIClass::updateSwitcher(int id, bool nValue, int clientId) {
if (id < cIndex && controls[id]->type == UI_SWITCHER) {
controls[id]->value = nValue ? 1 : 0;
String json;
@ -314,7 +430,7 @@ void ESPUIClass::updateSwitcher(int id, bool nValue, int clientId ) {
}
}
void ESPUIClass::updateSlider(int id, int nValue, int clientId ) {
void ESPUIClass::updateSlider(int id, int nValue, int clientId) {
if (id < cIndex && controls[id]->type == UI_SLIDER) {
controls[id]->value = nValue;
String json;
@ -327,8 +443,7 @@ void ESPUIClass::updateSlider(int id, int nValue, int clientId ) {
textThem(json, clientId);
} else {
if (debug)
Serial.println(String("Error: ") + String(id) +
String(" is no slider"));
Serial.println(String("Error: ") + String(id) + String(" is no slider"));
}
}
@ -342,12 +457,13 @@ void ESPUIClass::updateSwitcher(String label, bool nValue, int clientId) {
updateSwitcher(getIdByLabel(label), nValue, clientId);
}
// This is a hacky workaround because ESPAsyncWebServer does not have a function like this and it's clients array is private
void ESPUIClass::textThem(String text, int clientId){
// This is a hacky workaround because ESPAsyncWebServer does not have a function
// like this and it's clients array is private
void ESPUIClass::textThem(String text, int clientId) {
int tryId = 0;
for(int count = 0; count < this->ws->count();){
if(this->ws->hasClient(tryId)) {
if(clientId!=tryId){
for (int count = 0; count < this->ws->count();) {
if (this->ws->hasClient(tryId)) {
if (clientId != tryId) {
this->ws->client(tryId)->text(text);
}
count++;
@ -398,13 +514,17 @@ void ESPUIClass::begin(const char *_title) {
ui_title = _title;
server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/ws");
if(!SPIFFS.begin()) {
Serial.println("SPIFFS Mount Failed, PLEASE CHECK THE README ON HOW TO PREPARE YOUR ESP!!!!!!!");
return;
}
if(!SPIFFS.exists( "/index.htm")) {
Serial.println("Please read the README!!!!!!!, Make sure to ESPUI.prepareFileSystem() once in an empty sketch");
if (!SPIFFS.begin()) {
Serial.println("SPIFFS Mount Failed, PLEASE CHECK THE README ON HOW TO "
"PREPARE YOUR ESP!!!!!!!");
return;
}
listDir("/", 1);
if (!SPIFFS.exists("/index.htm")) {
Serial.println("Please read the README!!!!!!!, Make sure to "
"ESPUI.prepareFileSystem() once in an empty sketch");
return;
}
@ -418,9 +538,9 @@ void ESPUIClass::begin(const char *_title) {
});
server->onNotFound(
[](AsyncWebServerRequest *request) {
request->send(404);
});
[](AsyncWebServerRequest *request) {
request->send(404);
});
server->begin();
if (debug)

View File

@ -86,44 +86,44 @@ typedef struct Control {
class ESPUIClass {
public:
void begin(const char *_title); // Setup servers and page
void begin(const char *_title); // Setup servers and page
void prepareFileSystem(); // Initially preps the filesystem and loads a lot of stuff into SPIFFS
void prepareFileSystem(); // Initially preps the filesystem and loads a lot of stuff into SPIFFS
void list();
// Creating Elements
int label(const char *label, int color, String value = ""); // Create Label
int button(const char *label, void (*callBack)(Control, int), int color,
String value = ""); // Create Event Button
int switcher(const char *label, bool startState,
void (*callBack)(Control, int),
int color); // Create Toggle Button
int pad(const char *label, bool centerButton, void (*callBack)(Control, int),
int color); // Create Pad Control
int slider(const char *label, void (*callBack)(Control, int), int color, String value); // Create Slider Control
// Creating Elements
void label(const char *label, int color, String value = ""); // Create Label
void button(const char *label, void (*callBack)(Control, int), int color,
String value = ""); // Create Event Button
void switcher(const char *label, bool startState,
void (*callBack)(Control, int),
int color); // Create Toggle Button
void pad(const char *label, bool centerButton, void (*callBack)(Control, int),
int color); // Create Pad Control
void slider(const char *label, void (*callBack)(Control, int), int color, String value); // Create Slider Control
// Update Elements
void print(int id, String value);
void print(String label, String value);
// Update Elements
void print(int id, String value);
void print(String label, String value);
void updateSwitcher(int id, bool nValue, int clientId = -1);
void updateSwitcher(String label, bool nValue, int clientId = -1);
void updateSwitcher(int id, bool nValue, int clientId = -1);
void updateSwitcher(String label, bool nValue, int clientId = -1);
void updateSlider(int id, int nValue, int clientId = -1);
void updateSlider(String label, int nValue, int clientId = -1);
void updateSlider(int id, int nValue, int clientId = -1);
void updateSlider(String label, int nValue, int clientId = -1);
void textThem(String text, int clientId);
void textThem(String text, int clientId);
// Variables ---
const char *ui_title = "ESPUI"; // Store UI Title and Header Name
int cIndex = 0; // Control index
Control *controls[25];
void jsonDom(AsyncWebSocketClient *client);
int getIdByLabel(String label);
bool labelExists(String label);
// Variables ---
const char *ui_title = "ESPUI"; // Store UI Title and Header Name
int cIndex = 0; // Control index
Control *controls[25];
void jsonDom(AsyncWebSocketClient *client);
int getIdByLabel(String label);
bool labelExists(String label);
private:
AsyncWebServer *server;
AsyncWebSocket *ws;
AsyncWebServer *server;
AsyncWebSocket *ws;
};
extern ESPUIClass ESPUI;