From a0d1a8cbc4690ac730e75b1f74494742bdf43a4a Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 22 Mar 2025 11:53:05 -0400 Subject: [PATCH 1/5] Use enum class for json endpoint subtypes Part of the ongoing quest to migrate macro definitions to typed language constructs. This actually yields a small improvement in code size, likely from the byte->int conversion. --- wled00/json.cpp | 52 +++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 24988be15..0a307594a 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -2,15 +2,6 @@ #include "palettes.h" -#define JSON_PATH_STATE 1 -#define JSON_PATH_INFO 2 -#define JSON_PATH_STATE_INFO 3 -#define JSON_PATH_NODES 4 -#define JSON_PATH_PALETTES 5 -#define JSON_PATH_FXDATA 6 -#define JSON_PATH_NETWORKS 7 -#define JSON_PATH_EFFECTS 8 - /* * JSON API (De)serialization */ @@ -1036,16 +1027,20 @@ class LockedJsonResponse: public AsyncJsonResponse { void serveJson(AsyncWebServerRequest* request) { - byte subJson = 0; + enum class json_target { + all, state, info, state_info, nodes, effects, palettes, fxdata, networks + }; + json_target subJson = json_target::all; + const String& url = request->url(); - if (url.indexOf("state") > 0) subJson = JSON_PATH_STATE; - else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO; - else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO; - else if (url.indexOf(F("nodes")) > 0) subJson = JSON_PATH_NODES; - else if (url.indexOf(F("eff")) > 0) subJson = JSON_PATH_EFFECTS; - else if (url.indexOf(F("palx")) > 0) subJson = JSON_PATH_PALETTES; - else if (url.indexOf(F("fxda")) > 0) subJson = JSON_PATH_FXDATA; - else if (url.indexOf(F("net")) > 0) subJson = JSON_PATH_NETWORKS; + if (url.indexOf("state") > 0) subJson = json_target::state; + else if (url.indexOf("info") > 0) subJson = json_target::info; + else if (url.indexOf("si") > 0) subJson = json_target::state_info; + else if (url.indexOf(F("nodes")) > 0) subJson = json_target::nodes; + else if (url.indexOf(F("eff")) > 0) subJson = json_target::effects; + else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes; + else if (url.indexOf(F("fxda")) > 0) subJson = json_target::fxdata; + else if (url.indexOf(F("net")) > 0) subJson = json_target::networks; #ifdef WLED_ENABLE_JSONLIVE else if (url.indexOf("live") > 0) { serveLiveLeds(request); @@ -1070,32 +1065,33 @@ void serveJson(AsyncWebServerRequest* request) } // releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer) // make sure you delete "response" if no "request->send(response);" is made - LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary + LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::fxdata || subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary JsonVariant lDoc = response->getRoot(); switch (subJson) { - case JSON_PATH_STATE: + case json_target::state: serializeState(lDoc); break; - case JSON_PATH_INFO: + case json_target::info: serializeInfo(lDoc); break; - case JSON_PATH_NODES: + case json_target::nodes: serializeNodes(lDoc); break; - case JSON_PATH_PALETTES: + case json_target::palettes: serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break; - case JSON_PATH_EFFECTS: + case json_target::effects: serializeModeNames(lDoc); break; - case JSON_PATH_FXDATA: + case json_target::fxdata: serializeModeData(lDoc); break; - case JSON_PATH_NETWORKS: + case json_target::networks: serializeNetworks(lDoc); break; - default: //all + case json_target::state_info: + case json_target::all: JsonObject state = lDoc.createNestedObject("state"); serializeState(state); JsonObject info = lDoc.createNestedObject("info"); serializeInfo(info); - if (subJson != JSON_PATH_STATE_INFO) + if (subJson == json_target::all) { JsonArray effects = lDoc.createNestedArray(F("effects")); serializeModeNames(effects); // remove WLED-SR extensions from effect names From e21a09cec94ef607eaa286c21b42af6998a66381 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 23 Mar 2025 15:15:52 -0400 Subject: [PATCH 2/5] Separate FS write from serializeConfig Break the actual JSON assembly apart from the file writing code. This permits calling it in other contexts, allowing us to pull the live config data even if the filesystem is out of date. --- wled00/cfg.cpp | 24 ++++++++++++++---------- wled00/fcn_declare.h | 3 ++- wled00/improv.cpp | 2 +- wled00/wled.cpp | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 11862f83f..194ef4fbf 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -671,7 +671,7 @@ void deserializeConfigFromFS() { // call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving JsonObject empty = JsonObject(); UsermodManager::readFromConfig(empty); - serializeConfig(); + serializeConfigToFS(); // init Ethernet (in case default type is set at compile time) #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) initEthernet(); @@ -685,10 +685,10 @@ void deserializeConfigFromFS() { bool needsSave = deserializeConfig(root, true); releaseJSONBufferLock(); - if (needsSave) serializeConfig(); // usermods required new parameters + if (needsSave) serializeConfigToFS(); // usermods required new parameters } -void serializeConfig() { +void serializeConfigToFS() { serializeConfigSec(); DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); @@ -697,6 +697,17 @@ void serializeConfig() { JsonObject root = pDoc->to(); + serializeConfig(root); + + File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); + if (f) serializeJson(root, f); + f.close(); + releaseJSONBufferLock(); + + doSerializeConfig = false; +} + +void serializeConfig(JsonObject root) { JsonArray rev = root.createNestedArray("rev"); rev.add(1); //major settings revision rev.add(0); //minor settings revision @@ -1111,13 +1122,6 @@ void serializeConfig() { JsonObject usermods_settings = root.createNestedObject("um"); UsermodManager::addToConfig(usermods_settings); - - File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); - if (f) serializeJson(root, f); - f.close(); - releaseJSONBufferLock(); - - doSerializeConfig = false; } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 497a775ee..0f4666b30 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -27,7 +27,8 @@ void IRAM_ATTR touchButtonISR(); bool deserializeConfig(JsonObject doc, bool fromFS = false); void deserializeConfigFromFS(); bool deserializeConfigSec(); -void serializeConfig(); +void serializeConfig(JsonObject doc); +void serializeConfigToFS(); void serializeConfigSec(); template diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 197148b2b..0bc7a6698 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -272,5 +272,5 @@ void parseWiFiCommand(char* rpcData) { improvActive = 2; forceReconnect = true; - serializeConfig(); + serializeConfigToFS(); } \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 34caeefa3..b2cf26440 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -200,7 +200,7 @@ void WLED::loop() loadLedmap = -1; } yield(); - if (doSerializeConfig) serializeConfig(); + if (doSerializeConfig) serializeConfigToFS(); yield(); handleWs(); From 9c8f8c645ebd1f69f004a8c3716af4504ff394d6 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 23 Mar 2025 15:16:52 -0400 Subject: [PATCH 3/5] Rename 'doSerializeConfig' to 'configNeedsWrite' Clarify the name and usage of this flag, as the function name has changed out from underneath it. --- wled00/cfg.cpp | 2 +- wled00/dmx_input.cpp | 4 ++-- wled00/presets.cpp | 2 +- wled00/set.cpp | 4 ++-- wled00/wled.cpp | 6 +++--- wled00/wled.h | 2 +- wled00/wled_server.cpp | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 194ef4fbf..fa0397fc6 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -704,7 +704,7 @@ void serializeConfigToFS() { f.close(); releaseJSONBufferLock(); - doSerializeConfig = false; + configNeedsWrite = false; } void serializeConfig(JsonObject root) { diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 3197375f1..59467590c 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -22,7 +22,7 @@ void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) { const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum); DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality))); - doSerializeConfig = true; + configNeedsWrite = true; DEBUG_PRINTF("DMX personality changed to to: %d\n", DMXMode); } } @@ -40,7 +40,7 @@ void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header, if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) { const uint16_t addr = dmx_get_start_address(dmx->inputPortNum); DMXAddress = std::min(512, int(addr)); - doSerializeConfig = true; + configNeedsWrite = true; DEBUG_PRINTF("DMX start addr changed to: %d\n", DMXAddress); } } diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 54f052637..b749289bd 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -242,7 +242,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj) if (!sObj[FPSTR(bootPS)].isNull()) { bootPreset = sObj[FPSTR(bootPS)] | bootPreset; sObj.remove(FPSTR(bootPS)); - doSerializeConfig = true; + configNeedsWrite = true; } if (sObj.size()==0 || sObj["o"].isNull()) { // no "o" means not a playlist or custom API call, saving of state is async (not immediately) diff --git a/wled00/set.cpp b/wled00/set.cpp index 00333788d..c817f2553 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -805,8 +805,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) lastEditTime = millis(); // do not save if factory reset or LED settings (which are saved after LED re-init) - doSerializeConfig = subPage != SUBPAGE_LEDS && !(subPage == SUBPAGE_SEC && doReboot); - if (subPage == SUBPAGE_UM) doReboot = request->hasArg(F("RBT")); // prevent race condition on dual core system (set reboot here, after doSerializeConfig has been set) + configNeedsWrite = subPage != SUBPAGE_LEDS && !(subPage == SUBPAGE_SEC && doReboot); + if (subPage == SUBPAGE_UM) doReboot = request->hasArg(F("RBT")); // prevent race condition on dual core system (set reboot here, after configNeedsWrite has been set) #ifndef WLED_DISABLE_ALEXA if (subPage == SUBPAGE_SYNC) alexaInit(); #endif diff --git a/wled00/wled.cpp b/wled00/wled.cpp index b2cf26440..9683b432d 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -193,14 +193,14 @@ void WLED::loop() if (aligned) strip.makeAutoSegments(); else strip.fixInvalidSegments(); BusManager::setBrightness(bri); // fix re-initialised bus' brightness - doSerializeConfig = true; + configNeedsWrite = true; } if (loadLedmap >= 0) { strip.deserializeMap(loadLedmap); loadLedmap = -1; } yield(); - if (doSerializeConfig) serializeConfigToFS(); + if (configNeedsWrite) serializeConfigToFS(); yield(); handleWs(); @@ -223,7 +223,7 @@ void WLED::loop() } #endif - if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration + if (doReboot && (!doInitBusses || !configNeedsWrite)) // if busses have to be inited & saved, wait until next iteration reset(); // DEBUG serial logging (every 30s) diff --git a/wled00/wled.h b/wled00/wled.h index ea40c5dfe..8926967ff 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -877,7 +877,7 @@ WLED_GLOBAL byte errorFlag _INIT(0); WLED_GLOBAL String messageHead, messageSub; WLED_GLOBAL byte optionType; -WLED_GLOBAL bool doSerializeConfig _INIT(false); // flag to initiate saving of config +WLED_GLOBAL bool configNeedsWrite _INIT(false); // flag to initiate saving of config WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers WLED_GLOBAL bool psramSafe _INIT(true); // is it safe to use PSRAM (on ESP32 rev.1; compiler fix used "-mfix-esp32-psram-cache-issue") diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 77f4133c0..06750838f 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -328,7 +328,7 @@ void initServer() interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update serveJson(request); return; //if JSON contains "v" } else { - doSerializeConfig = true; //serializeConfig(); //Save new settings to FS + configNeedsWrite = true; //Save new settings to FS } } request->send(200, CONTENT_TYPE_JSON, F("{\"success\":true}")); From 22e2b6f3c517233eaca2e0972790e10d38db20b5 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 23 Mar 2025 15:18:08 -0400 Subject: [PATCH 4/5] Have json/cfg return live config Rather than reading the file off disk, have the json/cfg endpoint return the live config from system state data. This can improve UI behaviour as it can never be out of date or include values that do not apply to the current firmware install. --- wled00/json.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 0a307594a..c09b543f1 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1028,7 +1028,7 @@ class LockedJsonResponse: public AsyncJsonResponse { void serveJson(AsyncWebServerRequest* request) { enum class json_target { - all, state, info, state_info, nodes, effects, palettes, fxdata, networks + all, state, info, state_info, nodes, effects, palettes, fxdata, networks, config }; json_target subJson = json_target::all; @@ -1041,6 +1041,7 @@ void serveJson(AsyncWebServerRequest* request) else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes; else if (url.indexOf(F("fxda")) > 0) subJson = json_target::fxdata; else if (url.indexOf(F("net")) > 0) subJson = json_target::networks; + else if (url.indexOf(F("cfg")) > 0) subJson = json_target::config; #ifdef WLED_ENABLE_JSONLIVE else if (url.indexOf("live") > 0) { serveLiveLeds(request); @@ -1051,9 +1052,6 @@ void serveJson(AsyncWebServerRequest* request) request->send_P(200, FPSTR(CONTENT_TYPE_JSON), JSON_palette_names); return; } - else if (url.indexOf(F("cfg")) > 0 && handleFileRead(request, F("/cfg.json"))) { - return; - } else if (url.length() > 6) { //not just /json serveJsonError(request, 501, ERR_NOT_IMPL); return; @@ -1085,6 +1083,8 @@ void serveJson(AsyncWebServerRequest* request) serializeModeData(lDoc); break; case json_target::networks: serializeNetworks(lDoc); break; + case json_target::config: + serializeConfig(lDoc); break; case json_target::state_info: case json_target::all: JsonObject state = lDoc.createNestedObject("state"); From 36cb1cad369cdfe38adf12f08a6b6fc3707b32df Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 23 Mar 2025 15:20:16 -0400 Subject: [PATCH 5/5] settings_um: Use live config Use json/cfg for the usermod settings page. Should fix issues with outdated content when a new firmware is loaded. --- wled00/data/settings_um.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index c2f0ffbf2..b1505cac8 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -13,7 +13,7 @@ function S() { getLoc(); // load settings and insert values into DOM - fetch(getURL('/cfg.json'), { + fetch(getURL('/json/cfg'), { method: 'get' }) .then(res => {