From c3bf9c5d00df75cac7c6a353ff4b346413584112 Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Tue, 18 Jul 2023 19:49:58 +0200 Subject: [PATCH 1/2] reduce memory footprint --- README.md | 2 +- src/ESPUI.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++-- src/ESPUI.h | 24 ++++++++---------- src/ESPUIclient.cpp | 48 ++++++++++++++++++++++++++++++------ 4 files changed, 109 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index f4d5978..4659d70 100644 --- a/README.md +++ b/README.md @@ -495,7 +495,7 @@ ESPUI includes a range of advanced features that can customise your UIs. ### Dynamic Visibility -Cotrols can be made visible or invisible at runtime with the `updateVisibility()` function. +Controls can be made visible or invisible at runtime with the `updateVisibility()` function. ``` ESPUI.updateVisibility(controlId, false); diff --git a/src/ESPUI.cpp b/src/ESPUI.cpp index abbe40a..90f84e3 100644 --- a/src/ESPUI.cpp +++ b/src/ESPUI.cpp @@ -13,6 +13,61 @@ #include "dataTabbedcontentJS.h" #include "dataZeptoJS.h" +#if ESP8266 +#include +#endif + +static String heapInfo (const __FlashStringHelper* mode) +{ +#if ESP8266 + + uint32_t hfree; + uint32_t hmax; + uint8_t hfrag; + String result; + result.reserve(128); + +#ifdef UMM_HEAP_IRAM + // here esp8266 is configurerd to use an extra 16KB (i)ram + { + HeapSelectIram useInstructionRamHere; + ESP.getHeapStats(&hfree, &hmax, &hfrag); + } + result += F("IRAM: free: "); + result += hfree; + result += F(" max: "); + result += hmax; + result += F(" frag: "); + result += hfrag; + result += "%\n"; +#endif // !UMM_HEAP_IRAM + { + HeapSelectDram useRegularRamHere; + ESP.getHeapStats(&hfree, &hmax, &hfrag); + } + result += F("DRAM: free: "); + result += hfree; + result += F(" max: "); + result += hmax; + result += F(" frag: "); + result += hfrag; + result += "%\n"; + +#else // !ESP8266 + + result += ESP.getFreeHeap(); + result += ' '; + +#endif // !ESP8266 + + result += mode; + + return result; +} + + + + // ################# LITTLEFS functions #if defined(ESP32) void listDir(const char* dirname, uint8_t levels) @@ -1260,7 +1315,8 @@ void ESPUIClass::beginLITTLEFS(const char* _title, const char* username, const c return request->requestAuthentication(); } - request->send(200, "text/plain", String(ESP.getFreeHeap()) + " In LITTLEFS mode"); + request->send(200, "text/plain", heapInfo(F("In LITTLEFS mode"))); + }); server->onNotFound([this](AsyncWebServerRequest* request) { @@ -1418,7 +1474,7 @@ void ESPUIClass::begin(const char* _title, const char* username, const char* pas return request->requestAuthentication(); } - request->send(200, "text/plain", String(ESP.getFreeHeap()) + " In Memorymode"); + request->send(200, "text/plain", heapInfo(F("In Memorymode"))); }); server->onNotFound([this](AsyncWebServerRequest* request) { diff --git a/src/ESPUI.h b/src/ESPUI.h index fb42039..878097f 100644 --- a/src/ESPUI.h +++ b/src/ESPUI.h @@ -87,20 +87,20 @@ enum Verbosity : uint8_t class ESPUIClass { public: + +#ifdef ESP32 ESPUIClass() { - verbosity = Verbosity::Quiet; - jsonUpdateDocumentSize = 2000; - jsonInitialDocumentSize = 8000; - sliderContinuous = false; -#ifdef ESP32 ControlsSemaphore = xSemaphoreCreateMutex(); xSemaphoreGive(ControlsSemaphore); -#endif // def ESP32 } - unsigned int jsonUpdateDocumentSize; - unsigned int jsonInitialDocumentSize; - bool sliderContinuous; + SemaphoreHandle_t ControlsSemaphore = NULL; +#endif // def ESP32 + + unsigned int jsonUpdateDocumentSize = 2000; + unsigned int jsonInitialDocumentSize = 8000; + unsigned int jsonChunkNumberMax = 0; + bool sliderContinuous = false; void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); bool captivePortal = true; @@ -205,17 +205,13 @@ public: void jsonReload(); void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr, bool Updating = false); - Verbosity verbosity; + Verbosity verbosity = Verbosity::Quiet; AsyncWebServer* server; protected: friend class ESPUIclient; friend class ESPUIcontrol; -#ifdef ESP32 - SemaphoreHandle_t ControlsSemaphore = NULL; -#endif // def ESP32 - void RemoveToBeDeletedControls(); AsyncWebSocket* ws; diff --git a/src/ESPUIclient.cpp b/src/ESPUIclient.cpp index 9b7d561..9bce217 100644 --- a/src/ESPUIclient.cpp +++ b/src/ESPUIclient.cpp @@ -2,6 +2,42 @@ #include "ESPUIclient.h" #include "ESPUIcontrol.h" +// JSONSlave: +// helper to process exact JSON serialization size +// it takes ~2ms on esp8266 and avoid large String reallocation which is really worth the cost +class JSONSlave: public Print +{ +public: + size_t write (uint8_t c) override { counter++; return 1; } + size_t write (const uint8_t* buf, size_t count) override { counter += count; return count; } + size_t get_counter () { return counter; } + + static size_t serializedSize (JsonDocument& doc) + { + JSONSlave counter; + serializeJson(doc, counter); + return counter.get_counter(); + } + + static size_t serialize (JsonDocument& doc, String& str) + { + size_t s = serializedSize(doc) + 10; // 10 is paranoid + str.reserve(s); + serializeJson(doc, str); + return s; + } + + static String toString (JsonDocument& doc) + { + String str; + serialize(doc, str); + return str; + } + +protected: + size_t counter = 0; +}; + ESPUIclient::ESPUIclient(AsyncWebSocketClient * _client): client(_client) { @@ -309,7 +345,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex, elementcount++; control->MarshalControl(item, InUpdateMode); - if (rootDoc.overflowed()) + if (rootDoc.overflowed() || (ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0)) { // String("prepareJSONChunk: too much data in the message. Remove the last entry"); if (1 == elementcount) @@ -407,9 +443,8 @@ bool ESPUIclient::SendControlsToClient(uint16_t startidx, if (ESPUI.verbosity >= Verbosity::VerboseJSON) { Serial.println(F("ESPUIclient:SendControlsToClient: Sending elements --------->")); - String json; - serializeJson(document, json); - Serial.println(json); + serializeJson(document, Serial); + Serial.println(); } #endif @@ -454,10 +489,7 @@ bool ESPUIclient::SendJsonDocToWebSocket(DynamicJsonDocument& document) break; } - String json; - json.reserve(document.size() / 2); - json.clear(); - serializeJson(document, json); + String json = JSONSlave::toString(document); #if defined(DEBUG_ESPUI) if (ESPUI.verbosity >= Verbosity::VerboseJSON) From b45f4f73566ac8b87faba9f0c39fd07b217a727a Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Wed, 19 Jul 2023 12:07:25 +0200 Subject: [PATCH 2/2] update examples with esp8266 specifics --- examples/completeExample/completeExample.cpp | 20 ++++++++++++++++++++ examples/completeExample/completeExample.ino | 1 + examples/gui-generic-api/gui-generic-api.ino | 18 ++++++++++++++++++ examples/gui/gui.ino | 18 ++++++++++++++++++ examples/tabbedGui/tabbedGui.ino | 18 ++++++++++++++++++ src/ESPUI.h | 5 +++++ 6 files changed, 80 insertions(+) create mode 100644 examples/completeExample/completeExample.ino diff --git a/examples/completeExample/completeExample.cpp b/examples/completeExample/completeExample.cpp index ecae5c2..364a4f9 100644 --- a/examples/completeExample/completeExample.cpp +++ b/examples/completeExample/completeExample.cpp @@ -26,8 +26,18 @@ #include #include #else +// esp8266 #include #include +#include +#ifndef MMU_IRAM_HEAP +#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary) +#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap) +#warning then check http:///heap +#endif // MMU_IRAM_HEAP +#ifndef DEBUG_ESP_OOM +#error on ESP8266 and ESPUI, you must define OOM debug option when developping +#endif #endif //Settings @@ -62,6 +72,11 @@ volatile bool updates = false; // This is the main function which builds our GUI void setUpUI() { + +#ifdef ESP8266 + { HeapSelectIram doAllocationsInIRAM; +#endif + //Turn off verbose debugging ESPUI.setVerbosity(Verbosity::Quiet); @@ -274,6 +289,11 @@ void setUpUI() { //Finally, start up the UI. //This should only be called once we are connected to WiFi. ESPUI.begin(HOSTNAME); + +#ifdef ESP8266 + } // HeapSelectIram +#endif + } //This callback generates and applies inline styles to a bunch of controls to change their colour. diff --git a/examples/completeExample/completeExample.ino b/examples/completeExample/completeExample.ino new file mode 100644 index 0000000..ff7bd09 --- /dev/null +++ b/examples/completeExample/completeExample.ino @@ -0,0 +1 @@ +// placeholder diff --git a/examples/gui-generic-api/gui-generic-api.ino b/examples/gui-generic-api/gui-generic-api.ino index c268280..815e66d 100644 --- a/examples/gui-generic-api/gui-generic-api.ino +++ b/examples/gui-generic-api/gui-generic-api.ino @@ -8,7 +8,17 @@ DNSServer dnsServer; #if defined(ESP32) #include #else +// esp8266 #include +#include +#ifndef MMU_IRAM_HEAP +#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary) +#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap) +#warning then check http:///heap +#endif // MMU_IRAM_HEAP +#ifndef DEBUG_ESP_OOM +#error on ESP8266 and ESPUI, you must define OOM debug option when developping +#endif #endif const char* ssid = "ESPUI"; @@ -235,6 +245,10 @@ void setup(void) Serial.print("IP address: "); Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP()); +#ifdef ESP8266 + { HeapSelectIram doAllocationsInIRAM; +#endif + status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise); uint16_t select1 = ESPUI.addControl( @@ -281,6 +295,10 @@ void setup(void) */ ESPUI.begin("ESPUI Control"); + +#ifdef ESP8266 + } // HeapSelectIram +#endif } void loop(void) diff --git a/examples/gui/gui.ino b/examples/gui/gui.ino index f4315be..1aca2f1 100644 --- a/examples/gui/gui.ino +++ b/examples/gui/gui.ino @@ -8,7 +8,17 @@ DNSServer dnsServer; #if defined(ESP32) #include #else +// esp8266 #include +#include +#ifndef MMU_IRAM_HEAP +#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary) +#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap) +#warning then check http:///heap +#endif // MMU_IRAM_HEAP +#ifndef DEBUG_ESP_OOM +#error on ESP8266 and ESPUI, you must define OOM debug option when developping +#endif #endif const char* ssid = "ESPUI"; @@ -225,6 +235,10 @@ void setup(void) Serial.print("IP address: "); Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP()); +#ifdef ESP8266 + { HeapSelectIram doAllocationsInIRAM; +#endif + statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop"); millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0"); ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press"); @@ -257,6 +271,10 @@ void setup(void) * password, for example begin("ESPUI Control", "username", "password") */ ESPUI.begin("ESPUI Control"); + +#ifdef ESP8266 + } // HeapSelectIram +#endif } void loop(void) diff --git a/examples/tabbedGui/tabbedGui.ino b/examples/tabbedGui/tabbedGui.ino index f3ff7d8..7aa0b2f 100644 --- a/examples/tabbedGui/tabbedGui.ino +++ b/examples/tabbedGui/tabbedGui.ino @@ -8,7 +8,17 @@ DNSServer dnsServer; #if defined(ESP32) #include #else +// esp8266 #include +#include +#ifndef MMU_IRAM_HEAP +#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary) +#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap) +#warning then check http:///heap +#endif // MMU_IRAM_HEAP +#ifndef DEBUG_ESP_OOM +#error on ESP8266 and ESPUI, you must define OOM debug option when developping +#endif #endif const char* ssid = "ESPUI"; @@ -233,6 +243,10 @@ void setup(void) Serial.print("IP address: "); Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP()); +#ifdef ESP8266 + { HeapSelectIram doAllocationsInIRAM; +#endif + uint16_t tab1 = ESPUI.addControl(ControlType::Tab, "Settings 1", "Settings 1"); uint16_t tab2 = ESPUI.addControl(ControlType::Tab, "Settings 2", "Settings 2"); uint16_t tab3 = ESPUI.addControl(ControlType::Tab, "Settings 3", "Settings 3"); @@ -279,6 +293,10 @@ void setup(void) */ ESPUI.begin("ESPUI Control"); + +#ifdef ESP8266 + } // HeapSelectIram +#endif } void loop(void) diff --git a/src/ESPUI.h b/src/ESPUI.h index 878097f..43464fe 100644 --- a/src/ESPUI.h +++ b/src/ESPUI.h @@ -98,8 +98,13 @@ public: #endif // def ESP32 unsigned int jsonUpdateDocumentSize = 2000; +#ifdef ESP8266 + unsigned int jsonInitialDocumentSize = 2000; + unsigned int jsonChunkNumberMax = 5; +#else unsigned int jsonInitialDocumentSize = 8000; unsigned int jsonChunkNumberMax = 0; +#endif bool sliderContinuous = false; void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); bool captivePortal = true;