diff --git a/.clang-format b/.clang-format new file mode 100755 index 0000000..792a92e --- /dev/null +++ b/.clang-format @@ -0,0 +1,58 @@ +--- +# Based on Webkit style +BasedOnStyle: Webkit +IndentWidth: 4 +ColumnLimit: 120 +--- +Language: Cpp +Standard: Cpp11 +# Pointers aligned to the left +DerivePointerAlignment: false +PointerAlignment: Left +AccessModifierOffset: -4 +AllowShortFunctionsOnASingleLine: Inline +AlwaysBreakTemplateDeclarations: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakConstructorInitializers: BeforeColon +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +Cpp11BracedListStyle: true +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + # C++ standard headers (no .h) + - Regex: '<[[:alnum:]_-]+>' + Priority: 1 + # Extenal libraries (with .h) + - Regex: '<[[:alnum:]_./-]+>' + Priority: 2 + # Headers from same folder + - Regex: '"[[:alnum:]_.-]+"' + Priority: 3 + # Headers from other folders + - Regex: '"[[:alnum:]_/.-]+"' + Priority: 4 +IndentCaseLabels: false +NamespaceIndentation: All +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterTemplateKeyword: true +SpacesInAngles: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +UseTab: Never \ No newline at end of file diff --git a/src/ESPUI.cpp b/src/ESPUI.cpp index 858d291..3fa4b62 100644 --- a/src/ESPUI.cpp +++ b/src/ESPUI.cpp @@ -1,813 +1,921 @@ #include "ESPUI.h" -#include "dataIndexHTML.h" +#include -#include "dataNormalizeCSS.h" -#include "dataStyleCSS.h" +#include #include "dataControlsJS.h" #include "dataGraphJS.h" +#include "dataIndexHTML.h" +#include "dataNormalizeCSS.h" #include "dataSliderJS.h" +#include "dataStyleCSS.h" #include "dataTabbedcontentJS.h" #include "dataZeptoJS.h" -#include -#include - uint16_t Control::idCounter = 0; // ################# Spiffs functions #if defined(ESP32) -void listDir(const char *dirname, uint8_t levels) +void listDir(const char* dirname, uint8_t levels) { - if (ESPUI.verbosity) - { - Serial.printf("Listing directory: %s\n", dirname); - } - - File root = LittleFS.open(dirname); - - if (!root) - { +#if defined(DEBUG_ESPUI) if (ESPUI.verbosity) { - Serial.println("Failed to open directory"); + Serial.printf_P(F("Listing directory: %s\n"), dirname); } +#endif - return; - } + File root = LittleFS.open(dirname); - if (!root.isDirectory()) - { - if (ESPUI.verbosity) + if (!root) { - Serial.println("Not a directory"); +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("Failed to open directory")); + } +#endif + + return; } - return; - } - - File file = root.openNextFile(); - - while (file) - { - if (file.isDirectory()) + if (!root.isDirectory()) { - if (ESPUI.verbosity) - { - Serial.print(" DIR : "); - Serial.println(file.name()); - } +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("Not a directory")); + } +#endif - if (levels) - { - listDir(file.name(), levels - 1); - } + return; } - else + + File file = root.openNextFile(); + + while (file) { - if (ESPUI.verbosity) - { - Serial.print(" FILE: "); - Serial.print(file.name()); - Serial.print(" SIZE: "); - Serial.println(file.size()); - } - } + if (file.isDirectory()) + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F(" DIR : ")); + Serial.println(file.name()); + } +#endif - file = root.openNextFile(); - } + if (levels) + { + listDir(file.name(), levels - 1); + } + } + else + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F(" FILE: ")); + Serial.print(file.name()); + Serial.print(F(" SIZE: ")); + Serial.println(file.size()); + } +#endif + } + + file = root.openNextFile(); + } } #else -void listDir(const char *dirname, uint8_t levels) +void listDir(const char* dirname, uint8_t levels) { - // ignoring levels for esp8266 - Serial.printf("Listing directory: %s\n", dirname); + // ignoring levels for esp8266 + Serial.printf_P(PSTR("Listing directory: %s\n"), dirname); - String str = ""; - Dir dir = LittleFS.openDir("/"); + String str = ""; + Dir dir = LittleFS.openDir("/"); - while (dir.next()) - { - Serial.print(" FILE: "); - Serial.print(dir.fileName()); - Serial.print(" SIZE: "); - Serial.println(dir.fileSize()); - } + while (dir.next()) + { + Serial.print(F(" FILE: ")); + Serial.print(dir.fileName()); + Serial.print(F(" SIZE: ")); + Serial.println(dir.fileSize()); + } } #endif void ESPUIClass::list() { - if (!LittleFS.begin()) - { - Serial.println("SPIFFS Mount Failed"); - return; - } + if (!LittleFS.begin()) + { + Serial.println(F("SPIFFS Mount Failed")); + return; + } - listDir("/", 1); + listDir("/", 1); #if defined(ESP32) - Serial.println(LittleFS.totalBytes()); - Serial.println(LittleFS.usedBytes()); + Serial.println(LittleFS.totalBytes()); + Serial.println(LittleFS.usedBytes()); #else - FSInfo fs_info; - LittleFS.info(fs_info); + FSInfo fs_info; + LittleFS.info(fs_info); - Serial.println(fs_info.totalBytes); - Serial.println(fs_info.usedBytes); + Serial.println(fs_info.totalBytes); + Serial.println(fs_info.usedBytes); #endif } -void deleteFile(const char *path) +void deleteFile(const char* path) { - if (ESPUI.verbosity) - { - Serial.print(LittleFS.exists(path)); - } - - if (!LittleFS.exists(path)) - { +#if defined(DEBUG_ESPUI) if (ESPUI.verbosity) { - Serial.printf("File: %s does not exist, not deleting\n", path); + Serial.print(LittleFS.exists(path)); + } +#endif + + if (!LittleFS.exists(path)) + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.printf_P(PSTR("File: %s does not exist, not deleting\n"), path); + } +#endif + + return; } - return; - } - - if (ESPUI.verbosity) - { - Serial.printf("Deleting file: %s\n", path); - } - - if (LittleFS.remove(path)) - { +#if defined(DEBUG_ESPUI) if (ESPUI.verbosity) { - Serial.println("File deleted"); + Serial.printf_P(PSTR("Deleting file: %s\n"), path); } - } - else - { - if (ESPUI.verbosity) +#endif + + if (LittleFS.remove(path)) { - Serial.println("Delete failed"); +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("File deleted")); + } +#endif + } + else + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("Delete failed")); + } +#endif } - } } -void writeFile(const char *path, const char *data) +void writeFile(const char* path, const char* data) { - if (ESPUI.verbosity) - { - Serial.printf("Writing file: %s\n", path); - } - - File file = LittleFS.open(path, FILE_WRITE); - - if (!file) - { +#if defined(DEBUG_ESPUI) if (ESPUI.verbosity) { - Serial.println("Failed to open file for writing"); + Serial.printf_P(PSTR("Writing file: %s\n"), path); } +#endif - return; - } + File file = LittleFS.open(path, FILE_WRITE); + + if (!file) + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("Failed to open file for writing")); + } +#endif + + return; + } #if defined(ESP32) - if (file.print(data)) - { - if (ESPUI.verbosity) + if (file.print(data)) { - Serial.println("File written"); +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("File written")); + } +#endif } - } - else - { - if (ESPUI.verbosity) + else { - Serial.println("Write failed"); +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("Write failed")); + } +#endif } - } #else - if (file.print(FPSTR(data))) - { - if (ESPUI.verbosity) + if (file.print(FPSTR(data))) { - Serial.println("File written"); +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("File written")); + } +#endif } - } - else - { - if (ESPUI.verbosity) + else { - Serial.println("Write failed"); +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("Write failed")); + } +#endif } - } #endif - file.close(); + file.close(); } // end Spiffs functions void ESPUIClass::prepareFileSystem() { - // this function should only be used once + // this function should only be used once - if (this->verbosity) - { - Serial.println("About to prepare filesystem..."); - } - -#if defined(ESP32) - LittleFS.format(); - - if (!LittleFS.begin(true)) - { +#if defined(DEBUG_ESPUI) if (this->verbosity) { - Serial.println("SPIFFS Mount Failed"); + Serial.println(F("About to prepare filesystem...")); + } +#endif + +#if defined(ESP32) + LittleFS.format(); + + if (!LittleFS.begin(true)) + { +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + Serial.println(F("SPIFFS Mount Failed")); + } +#endif + + return; } - return; - } - - if (this->verbosity) - { - listDir("/", 1); - Serial.println("SPIFFS Mount ESP32 Done"); - } +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + listDir("/", 1); + Serial.println(F("SPIFFS Mount ESP32 Done")); + } +#endif #else - LittleFS.format(); - LittleFS.begin(); + LittleFS.format(); + LittleFS.begin(); - if (this->verbosity) - { - Serial.println("SPIFFS Mount ESP8266 Done"); - } +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + Serial.println(F("SPIFFS Mount ESP8266 Done")); + } +#endif #endif - deleteFile("/index.htm"); + deleteFile("/index.htm"); - deleteFile("/css/style.css"); - deleteFile("/css/normalize.css"); + deleteFile("/css/style.css"); + deleteFile("/css/normalize.css"); - deleteFile("/js/zepto.min.js"); - deleteFile("/js/controls.js"); - deleteFile("/js/slider.js"); - deleteFile("/js/graph.js"); - deleteFile("/js/tabbedcontent.js"); + deleteFile("/js/zepto.min.js"); + deleteFile("/js/controls.js"); + deleteFile("/js/slider.js"); + deleteFile("/js/graph.js"); + deleteFile("/js/tabbedcontent.js"); - if (this->verbosity) - { - Serial.println("Cleanup done"); - } +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + Serial.println(F("Cleanup done")); + } +#endif - // Now write - writeFile("/index.htm", HTML_INDEX); + // Now write + writeFile("/index.htm", HTML_INDEX); - writeFile("/css/style.css", CSS_STYLE); - writeFile("/css/normalize.css", CSS_NORMALIZE); + writeFile("/css/style.css", CSS_STYLE); + writeFile("/css/normalize.css", CSS_NORMALIZE); - writeFile("/js/zepto.min.js", JS_ZEPTO); - writeFile("/js/controls.js", JS_CONTROLS); - writeFile("/js/slider.js", JS_SLIDER); - writeFile("/js/graph.js", JS_GRAPH); + writeFile("/js/zepto.min.js", JS_ZEPTO); + writeFile("/js/controls.js", JS_CONTROLS); + writeFile("/js/slider.js", JS_SLIDER); + writeFile("/js/graph.js", JS_GRAPH); - writeFile("/js/tabbedcontent.js", JS_TABBEDCONTENT); + writeFile("/js/tabbedcontent.js", JS_TABBEDCONTENT); - if (this->verbosity) - { - Serial.println("Done Initializing filesystem :-)"); - } +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + Serial.println(F("Done Initializing filesystem :-)")); + } +#endif #if defined(ESP32) - if (this->verbosity) - { - listDir("/", 1); - } +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + listDir("/", 1); + } +#endif #endif - LittleFS.end(); + LittleFS.end(); } // Handle Websockets Communication -void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) +void onWsEvent( + AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) { - switch (type) - { - case WS_EVT_DISCONNECT: - { - if (ESPUI.verbosity) + switch (type) { - Serial.printf("Disconnected!\n"); + case WS_EVT_DISCONNECT: + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F("Disconnected!\n")); + } +#endif + + break; } + case WS_EVT_PONG: + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F("Received PONG!\n")); + } +#endif + + break; + } + + case WS_EVT_ERROR: + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F("WebSocket Error!\n")); + } +#endif + + break; + } + + case WS_EVT_CONNECT: + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F("Connected: ")); + Serial.println(client->id()); + } +#endif + + ESPUI.jsonDom(client); + +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("JSON Data Sent to Client!")); + } +#endif + } break; - } - case WS_EVT_PONG: - { - if (ESPUI.verbosity) + case WS_EVT_DATA: { - Serial.printf("Received PONG!\n"); - } + String msg = ""; + msg.reserve(len + 1); + for (size_t i = 0; i < len; i++) + { + msg += (char)data[i]; + } + + uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt(); + +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity >= Verbosity::VerboseJSON) + { + Serial.print(F("WS rec: ")); + Serial.println(msg); + Serial.print(F("WS recognised ID: ")); + Serial.println(id); + } +#endif + + Control* c = ESPUI.getControl(id); + + if (c == nullptr) + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F("No control found for ID ")); + Serial.println(id); + } +#endif + + return; + } + + if (c->callback == nullptr) + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.print(F("No callback found for ID ")); + Serial.println(id); + } +#endif + + return; + } + + if (msg.startsWith(F("bdown:"))) + { + c->callback(c, B_DOWN); + } + else if (msg.startsWith(F("bup:"))) + { + c->callback(c, B_UP); + } + else if (msg.startsWith(F("pfdown:"))) + { + c->callback(c, P_FOR_DOWN); + } + else if (msg.startsWith(F("pfup:"))) + { + c->callback(c, P_FOR_UP); + } + else if (msg.startsWith(F("pldown:"))) + { + c->callback(c, P_LEFT_DOWN); + } + else if (msg.startsWith(F("plup:"))) + { + c->callback(c, P_LEFT_UP); + } + else if (msg.startsWith(F("prdown:"))) + { + c->callback(c, P_RIGHT_DOWN); + } + else if (msg.startsWith(F("prup:"))) + { + c->callback(c, P_RIGHT_UP); + } + else if (msg.startsWith(F("pbdown:"))) + { + c->callback(c, P_BACK_DOWN); + } + else if (msg.startsWith(F("pbup:"))) + { + c->callback(c, P_BACK_UP); + } + else if (msg.startsWith(F("pcdown:"))) + { + c->callback(c, P_CENTER_DOWN); + } + else if (msg.startsWith(F("pcup:"))) + { + c->callback(c, P_CENTER_UP); + } + else if (msg.startsWith(F("sactive:"))) + { + c->value = "1"; + ESPUI.updateControl(c, client->id()); + c->callback(c, S_ACTIVE); + } + else if (msg.startsWith(F("sinactive:"))) + { + c->value = "0"; + ESPUI.updateControl(c, client->id()); + c->callback(c, S_INACTIVE); + } + else if (msg.startsWith(F("slvalue:"))) + { + c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); + ESPUI.updateControl(c, client->id()); + c->callback(c, SL_VALUE); + } + else if (msg.startsWith(F("nvalue:"))) + { + c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); + ESPUI.updateControl(c, client->id()); + c->callback(c, N_VALUE); + } + else if (msg.startsWith(F("tvalue:"))) + { + c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); + ESPUI.updateControl(c, client->id()); + c->callback(c, T_VALUE); + } + else if (msg.startsWith(F("svalue:"))) + { + c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); + ESPUI.updateControl(c, client->id()); + c->callback(c, S_VALUE); + } + else + { +#if defined(DEBUG_ESPUI) + if (ESPUI.verbosity) + { + Serial.println(F("Malformated message from the websocket")); + } +#endif + } + } break; - } - case WS_EVT_ERROR: - { - if (ESPUI.verbosity) - { - Serial.printf("WebSocket Error!\n"); + default: + break; } +} - break; - } +uint16_t ESPUIClass::addControl(ControlType type, const char* label, const String& value, ControlColor color, + uint16_t parentControl, void (*callback)(Control*, int)) +{ + Control* control = new Control(type, label, callback, value, color, parentControl); - case WS_EVT_CONNECT: - { - if (ESPUI.verbosity) + if (this->controls == nullptr) { - Serial.print("Connected: "); - Serial.println(client->id()); - } - - ESPUI.jsonDom(client); - - if (ESPUI.verbosity) - { - Serial.println("JSON Data Sent to Client!"); - } - } - break; - - case WS_EVT_DATA: - { - String msg = ""; - msg.reserve(len + 1); - - for (size_t i = 0; i < len; i++) - { - msg += (char)data[i]; - } - - uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt(); - - if (ESPUI.verbosity >= Verbosity::VerboseJSON) - { - Serial.print("WS rec: "); - Serial.println(msg); - Serial.print("WS recognised ID: "); - Serial.println(id); - } - - Control *c = ESPUI.getControl(id); - - if (c == nullptr) - { - if (ESPUI.verbosity) - { - Serial.print("No control found for ID "); - Serial.println(id); - } - - return; - } - - if (c->callback == nullptr) - { - if (ESPUI.verbosity) - { - Serial.print("No callback found for ID "); - Serial.println(id); - } - - return; - } - - if (msg.startsWith("bdown:")) - { - c->callback(c, B_DOWN); - } - else if (msg.startsWith("bup:")) - { - c->callback(c, B_UP); - } - else if (msg.startsWith("pfdown:")) - { - c->callback(c, P_FOR_DOWN); - } - else if (msg.startsWith("pfup:")) - { - c->callback(c, P_FOR_UP); - } - else if (msg.startsWith("pldown:")) - { - c->callback(c, P_LEFT_DOWN); - } - else if (msg.startsWith("plup:")) - { - c->callback(c, P_LEFT_UP); - } - else if (msg.startsWith("prdown:")) - { - c->callback(c, P_RIGHT_DOWN); - } - else if (msg.startsWith("prup:")) - { - c->callback(c, P_RIGHT_UP); - } - else if (msg.startsWith("pbdown:")) - { - c->callback(c, P_BACK_DOWN); - } - else if (msg.startsWith("pbup:")) - { - c->callback(c, P_BACK_UP); - } - else if (msg.startsWith("pcdown:")) - { - c->callback(c, P_CENTER_DOWN); - } - else if (msg.startsWith("pcup:")) - { - c->callback(c, P_CENTER_UP); - } - else if (msg.startsWith("sactive:")) - { - c->value = "1"; - ESPUI.updateControl(c, client->id()); - c->callback(c, S_ACTIVE); - } - else if (msg.startsWith("sinactive:")) - { - c->value = "0"; - ESPUI.updateControl(c, client->id()); - c->callback(c, S_INACTIVE); - } - else if (msg.startsWith("slvalue:")) - { - c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); - ESPUI.updateControl(c, client->id()); - c->callback(c, SL_VALUE); - } - else if (msg.startsWith("nvalue:")) - { - c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); - ESPUI.updateControl(c, client->id()); - c->callback(c, N_VALUE); - } - else if (msg.startsWith("tvalue:")) - { - c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); - ESPUI.updateControl(c, client->id()); - c->callback(c, T_VALUE); - } - else if (msg.startsWith("svalue:")) - { - c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':')); - ESPUI.updateControl(c, client->id()); - c->callback(c, S_VALUE); + this->controls = control; } else { - if (ESPUI.verbosity) - { - Serial.println("Malformated message from the websocket"); - } - } - } - break; + Control* iterator = this->controls; - default: - break; - } -} + while (iterator->next != nullptr) + { + iterator = iterator->next; + } -uint16_t ESPUIClass::addControl(ControlType type, const char *label, String value, ControlColor color, uint16_t parentControl, - void (*callback)(Control *, int)) -{ - Control *control = new Control(type, label, callback, value, color, parentControl); - - if (this->controls == nullptr) - { - this->controls = control; - } - else - { - Control *iterator = this->controls; - - while (iterator->next != nullptr) - { - iterator = iterator->next; + iterator->next = control; } - iterator->next = control; - } - - return control->id; + return control->id; } bool ESPUIClass::removeControl(uint16_t id, bool force_reload_ui) { - if (nullptr == this->controls) + if (nullptr == this->controls) + return false; + + Control* it = this->controls; + + if (id == it->id) + { + this->controls = it->next; + delete it; + if (force_reload_ui) + { + jsonReload(); + } + else + { + jsonDom(); + } + return true; + } + + Control* it_next = it->next; + while (nullptr != it_next && id != it_next->id) + { + it = it_next; + it_next = it_next->next; + } + + if (nullptr != it_next) + { + it->next = it_next->next; + delete it_next; + if (force_reload_ui) + { + jsonReload(); + } + else + { + jsonDom(); // resends to all + } + return true; + } + return false; +} - Control *it = this->controls; +uint16_t ESPUIClass::label(const char* label, ControlColor color, const String& value) +{ + return addControl(ControlType::Label, label, value, color); +} - if (id == it->id) - { - this->controls = it->next; - delete it; - if (force_reload_ui) +uint16_t ESPUIClass::graph(const char* label, ControlColor color) +{ + return addControl(ControlType::Graph, label, "", color); +} + +uint16_t ESPUIClass::slider( + const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min, int max) +{ + uint16_t sliderId = addControl(ControlType::Slider, label, String(value), color, Control::noParent, callback); + addControl(ControlType::Min, label, String(min), ControlColor::None, sliderId); + addControl(ControlType::Max, label, String(max), ControlColor::None, sliderId); + + return sliderId; +} + +uint16_t ESPUIClass::button(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value) +{ + return addControl(ControlType::Button, label, value, color, Control::noParent, callback); +} + +uint16_t ESPUIClass::switcher(const char* label, void (*callback)(Control*, int), ControlColor color, bool startState) +{ + return addControl(ControlType::Switcher, label, startState ? "1" : "0", color, Control::noParent, callback); +} + +uint16_t ESPUIClass::pad(const char* label, void (*callback)(Control*, int), ControlColor color) +{ + return addControl(ControlType::Pad, label, "", color, Control::noParent, callback); +} +uint16_t ESPUIClass::padWithCenter(const char* label, void (*callback)(Control*, int), ControlColor color) +{ + return addControl(ControlType::PadWithCenter, label, "", color, Control::noParent, callback); +} + +uint16_t ESPUIClass::number( + const char* label, void (*callback)(Control*, int), ControlColor color, int number, int min, int max) +{ + uint16_t numberId = addControl(ControlType::Number, label, String(number), color, Control::noParent, callback); + addControl(ControlType::Min, label, String(min), ControlColor::None, numberId); + addControl(ControlType::Max, label, String(max), ControlColor::None, numberId); + return numberId; +} + +uint16_t ESPUIClass::gauge(const char* label, ControlColor color, int number, int min, int max) +{ + uint16_t numberId = addControl(ControlType::Gauge, label, String(number), color, Control::noParent); + addControl(ControlType::Min, label, String(min), ControlColor::None, numberId); + addControl(ControlType::Max, label, String(max), ControlColor::None, numberId); + return numberId; +} + +uint16_t ESPUIClass::accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color) +{ + return addControl(ControlType::Accel, label, "", color, Control::noParent, callback); +} + +uint16_t ESPUIClass::text(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value) +{ + return addControl(ControlType::Text, label, value, color, Control::noParent, callback); +} + +Control* ESPUIClass::getControl(uint16_t id) +{ + Control* control = this->controls; + + while (control != nullptr) { - jsonReload(); - } - else - { - jsonDom(); - } - return true; - } + if (control->id == id) + { + return control; + } - Control *it_next = it->next; - while (nullptr != it_next && id != it_next->id) - { - it = it_next; - it_next = it_next->next; - } - - if (nullptr != it_next) - { - it->next = it_next->next; - delete it_next; - if (force_reload_ui) - { - jsonReload(); - } - else - { - jsonDom(); // resends to all - } - return true; - } - - return false; -} - -uint16_t ESPUIClass::label(const char *label, ControlColor color, String value) { return addControl(ControlType::Label, label, value, color); } - -uint16_t ESPUIClass::graph(const char *label, ControlColor color) { return addControl(ControlType::Graph, label, "", color); } - -uint16_t ESPUIClass::slider(const char *label, void (*callback)(Control *, int), ControlColor color, int value, int min, int max) -{ - uint16_t sliderId = addControl(ControlType::Slider, label, String(value), color, Control::noParent, callback); - addControl(ControlType::Min, label, String(min), ControlColor::None, sliderId); - addControl(ControlType::Max, label, String(max), ControlColor::None, sliderId); - - return sliderId; -} - -uint16_t ESPUIClass::button(const char *label, void (*callback)(Control *, int), ControlColor color, String value) -{ - return addControl(ControlType::Button, label, value, color, Control::noParent, callback); -} - -uint16_t ESPUIClass::switcher(const char *label, void (*callback)(Control *, int), ControlColor color, bool startState) -{ - return addControl(ControlType::Switcher, label, startState ? "1" : "0", color, Control::noParent, callback); -} - -uint16_t ESPUIClass::pad(const char *label, void (*callback)(Control *, int), ControlColor color) -{ - return addControl(ControlType::Pad, label, "", color, Control::noParent, callback); -} -uint16_t ESPUIClass::padWithCenter(const char *label, void (*callback)(Control *, int), ControlColor color) -{ - return addControl(ControlType::PadWithCenter, label, "", color, Control::noParent, callback); -} - -uint16_t ESPUIClass::number(const char *label, void (*callback)(Control *, int), ControlColor color, int number, int min, int max) -{ - uint16_t numberId = addControl(ControlType::Number, label, String(number), color, Control::noParent, callback); - addControl(ControlType::Min, label, String(min), ControlColor::None, numberId); - addControl(ControlType::Max, label, String(max), ControlColor::None, numberId); - return numberId; -} - -uint16_t ESPUIClass::gauge(const char *label, ControlColor color, int number, int min, int max) -{ - uint16_t numberId = addControl(ControlType::Gauge, label, String(number), color, Control::noParent); - addControl(ControlType::Min, label, String(min), ControlColor::None, numberId); - addControl(ControlType::Max, label, String(max), ControlColor::None, numberId); - return numberId; -} - -uint16_t ESPUIClass::accelerometer(const char *label, void (*callback)(Control *, int), ControlColor color) -{ - return addControl(ControlType::Accel, label, "", color, Control::noParent, callback); -} - -uint16_t ESPUIClass::text(const char *label, void (*callback)(Control *, int), ControlColor color, String value) -{ - return addControl(ControlType::Text, label, value, color, Control::noParent, callback); -} - -Control *ESPUIClass::getControl(uint16_t id) -{ - Control *control = this->controls; - - while (control != nullptr) - { - if (control->id == id) - { - return control; + control = control->next; } - control = control->next; - } - - return nullptr; + return nullptr; } -void ESPUIClass::updateControl(Control *control, int clientId) +void ESPUIClass::updateControl(Control* control, int clientId) { - if (!control) - { - return; - } + if (!control) + { + return; + } - String json; - DynamicJsonDocument document(jsonUpdateDocumentSize); - JsonObject root = document.to(); + String json; + DynamicJsonDocument document(jsonUpdateDocumentSize); + JsonObject root = document.to(); - root["type"] = (int)control->type + ControlType::UpdateOffset; - root["value"] = control->value; - root["id"] = control->id; - root["color"] = (int)control->color; - serializeJson(document, json); + root["type"] = (int)control->type + ControlType::UpdateOffset; + root["value"] = control->value; + root["id"] = control->id; + root["color"] = (int)control->color; + serializeJson(document, json); - if (this->verbosity >= Verbosity::VerboseJSON) - { - Serial.println(json); - } - - if (clientId < 0) - { +#if defined(DEBUG_ESPUI) if (this->verbosity >= Verbosity::VerboseJSON) { - Serial.println("TextAll"); + Serial.println(json); } - this->ws->textAll(json); - return; - } - // This is a hacky workaround because ESPAsyncWebServer does not have a - // function like this and it's clients array is private - int tryId = 0; +#endif - for (int count = 0; count < this->ws->count();) - { - if (this->ws->hasClient(tryId)) + if (clientId < 0) { - if (clientId != tryId) - { - this->ws->client(tryId)->text(json); - } - - count++; +#if defined(DEBUG_ESPUI) + if (this->verbosity >= Verbosity::VerboseJSON) + { + Serial.println(F("TextAll")); + } +#endif + this->ws->textAll(json); + return; } + // This is a hacky workaround because ESPAsyncWebServer does not have a + // function like this and it's clients array is private + int tryId = 0; - tryId++; - } + for (int count = 0; count < this->ws->count();) + { + if (this->ws->hasClient(tryId)) + { + if (clientId != tryId) + { + this->ws->client(tryId)->text(json); + } + + count++; + } + + tryId++; + } } void ESPUIClass::updateControl(uint16_t id, int clientId) { - Control *control = getControl(id); + Control* control = getControl(id); - if (!control) - { - if (this->verbosity) + if (!control) { - Serial.println(String("Error: There is no control with ID ") + String(id)); +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + Serial.printf_P(PSTR("Error: There is no control with ID %d"), id); + } +#endif + return; } - return; - } - updateControl(control, clientId); + updateControl(control, clientId); } -void ESPUIClass::updateControlValue(Control *control, String value, int clientId) +void ESPUIClass::updateControlValue(Control* control, const String& value, int clientId) { - if (!control) - { - return; - } - - control->value = value; - updateControl(control, clientId); -} - -void ESPUIClass::updateControlValue(uint16_t id, String value, int clientId) -{ - Control *control = getControl(id); - - if (!control) - { - if (this->verbosity) + if (!control) { - Serial.println(String("Error: There is no control with ID ") + String(id)); + return; } - return; - } - updateControlValue(control, value, clientId); + control->value = value; + updateControl(control, clientId); } -void ESPUIClass::print(uint16_t id, String value) { updateControlValue(id, value); } +void ESPUIClass::updateControlValue(uint16_t id, const String& value, int clientId) +{ + Control* control = getControl(id); -void ESPUIClass::updateLabel(uint16_t id, String value) { updateControlValue(id, value); } + if (!control) + { +#if defined(DEBUG_ESPUI) + if (this->verbosity) + { + Serial.printf_P(PSTR("Error: There is no control with ID %d"), id); + } +#endif + return; + } -void ESPUIClass::updateSlider(uint16_t id, int nValue, int clientId) { updateControlValue(id, String(nValue), clientId); } + updateControlValue(control, value, clientId); +} -void ESPUIClass::updateSwitcher(uint16_t id, bool nValue, int clientId) { updateControlValue(id, String(nValue ? "1" : "0"), clientId); } +void ESPUIClass::print(uint16_t id, const String& value) +{ + updateControlValue(id, value); +} -void ESPUIClass::updateNumber(uint16_t id, int number, int clientId) { updateControlValue(id, String(number), clientId); } +void ESPUIClass::updateLabel(uint16_t id, const String& value) +{ + updateControlValue(id, value); +} -void ESPUIClass::updateText(uint16_t id, String text, int clientId) { updateControlValue(id, text, clientId); } +void ESPUIClass::updateSlider(uint16_t id, int nValue, int clientId) +{ + updateControlValue(id, String(nValue), clientId); +} -void ESPUIClass::updateSelect(uint16_t id, String text, int clientId) { updateControlValue(id, text, clientId); } +void ESPUIClass::updateSwitcher(uint16_t id, bool nValue, int clientId) +{ + updateControlValue(id, String(nValue ? "1" : "0"), clientId); +} -void ESPUIClass::updateGauge(uint16_t id, int number, int clientId) { updateControlValue(id, String(number), clientId); } +void ESPUIClass::updateNumber(uint16_t id, int number, int clientId) +{ + updateControlValue(id, String(number), clientId); +} + +void ESPUIClass::updateText(uint16_t id, const String& text, int clientId) +{ + updateControlValue(id, text, clientId); +} + +void ESPUIClass::updateSelect(uint16_t id, const String& text, int clientId) +{ + updateControlValue(id, text, clientId); +} + +void ESPUIClass::updateGauge(uint16_t id, int number, int clientId) +{ + updateControlValue(id, String(number), clientId); +} void ESPUIClass::clearGraph(uint16_t id, int clientId) {} void ESPUIClass::addGraphPoint(uint16_t id, int nValue, int clientId) { - Control *control = getControl(id); - if (!control) - { - return; - } - - String json; - DynamicJsonDocument document(jsonUpdateDocumentSize); - JsonObject root = document.to(); - - root["type"] = (int)ControlType::GraphPoint; - root["value"] = nValue; - root["id"] = control->id; - serializeJson(document, json); - - if (this->verbosity >= Verbosity::VerboseJSON) - { - Serial.println(json); - } - - if (clientId < 0) - { - this->ws->textAll(json); - return; - } - // This is a hacky workaround because ESPAsyncWebServer does not have a - // function like this and it's clients array is private - int tryId = 0; - - for (int count = 0; count < this->ws->count();) - { - if (this->ws->hasClient(tryId)) + Control* control = getControl(id); + if (!control) { - if (clientId != tryId) - { - this->ws->client(tryId)->text(json); - - if (this->verbosity >= Verbosity::VerboseJSON) - { - Serial.println(json); - } - } - - count++; + return; } - tryId++; - } + String json; + DynamicJsonDocument document(jsonUpdateDocumentSize); + JsonObject root = document.to(); + + root["type"] = (int)ControlType::GraphPoint; + root["value"] = nValue; + root["id"] = control->id; + serializeJson(document, json); + +#if defined(DEBUG_ESPUI) + if (this->verbosity >= Verbosity::VerboseJSON) + { + Serial.println(json); + } +#endif + + if (clientId < 0) + { + this->ws->textAll(json); + return; + } + // This is a hacky workaround because ESPAsyncWebServer does not have a + // function like this and it's clients array is private + int tryId = 0; + + for (int count = 0; count < this->ws->count();) + { + if (this->ws->hasClient(tryId)) + { + if (clientId != tryId) + { + this->ws->client(tryId)->text(json); + +#if defined(DEBUG_ESPUI) + if (this->verbosity >= Verbosity::VerboseJSON) + { + Serial.println(json); + } +#endif + } + + count++; + } + + tryId++; + } } /* Convert & Transfer Arduino elements to JSON elements @@ -815,302 +923,328 @@ Initially this function used to send the control element data individually. Due to a change in the ESPAsyncWebserver library this had top be changed to be sent as one blob at the beginning. Therefore a new type is used as well */ -void ESPUIClass::jsonDom(AsyncWebSocketClient *client) +void ESPUIClass::jsonDom(AsyncWebSocketClient* client) { - String json; - DynamicJsonDocument document(jsonInitialDocumentSize); - document["type"] = (int)UI_INITIAL_GUI; - document["sliderContinuous"] = sliderContinuous; - JsonArray items = document.createNestedArray("controls"); + String json; + DynamicJsonDocument document(jsonInitialDocumentSize); + document["type"] = (int)UI_INITIAL_GUI; + document["sliderContinuous"] = sliderContinuous; + JsonArray items = document.createNestedArray("controls"); - Control *control = this->controls; + Control* control = this->controls; - JsonObject titleItem = items.createNestedObject(); - titleItem["type"] = (int)UI_TITLE; - titleItem["label"] = ui_title; + JsonObject titleItem = items.createNestedObject(); + titleItem["type"] = (int)UI_TITLE; + titleItem["label"] = ui_title; - while (control != nullptr) - { - JsonObject item = items.createNestedObject(); - - item["id"] = String(control->id); - item["type"] = (int)control->type; - item["label"] = control->label; - item["value"] = String(control->value); - item["color"] = (int)control->color; - - if (control->parentControl != Control::noParent) + while (control != nullptr) { - item["parentControl"] = String(control->parentControl); + JsonObject item = items.createNestedObject(); + + item["id"] = String(control->id); + item["type"] = (int)control->type; + item["label"] = control->label; + item["value"] = String(control->value); + item["color"] = (int)control->color; + + if (control->parentControl != Control::noParent) + { + item["parentControl"] = String(control->parentControl); + } + + // special case for selects: to preselect an option, you have to add + // "selected" to