diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index a0cc3c4461..34b9436710 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -71,29 +71,50 @@ void WS2812FX::setUpMatrix() { // allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel) char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); bool isFile = WLED_FS.exists(fileName); - size_t gapSize = 0; int8_t *gapTable = nullptr; - if (isFile && requestJSONBufferLock(JSON_LOCK_LEDGAP)) { + if (isFile) { DEBUG_PRINT(F("Reading LED gap from ")); DEBUG_PRINTLN(fileName); - // read the array into global JSON buffer - if (readObjectFromFile(fileName, nullptr, pDoc)) { + gapTable = static_cast(p_malloc(matrixSize)); + if (gapTable) { // the array is similar to ledmap, except it has only 3 values: // -1 ... missing pixel (do not increase pixel count) // 0 ... inactive pixel (it does count, but should be mapped out (-1)) // 1 ... active pixel (it will count and will be mapped) - JsonArray map = pDoc->as(); - gapSize = map.size(); - if (!map.isNull() && gapSize >= matrixSize) { // not an empty map - gapTable = static_cast(p_malloc(gapSize)); - if (gapTable) for (size_t i = 0; i < gapSize; i++) { - gapTable[i] = constrain(map[i], -1, 1); + // read entries directly from the file, one number at a time + // (no JSON buffer / pDoc needed — the file is a plain JSON array) + // follows the same parsing pattern used by deserializeMap() for ledmap.json + size_t gapIdx = 0; + File f = WLED_FS.open(fileName, "r"); + if (f) { + f.find('['); // skip to start of array + while (f.available() && gapIdx < matrixSize) { + char number[8]; + size_t numRead = f.readBytesUntil(',', number, sizeof(number) - 1); // last entry reads up to ']' (no trailing comma) + number[numRead] = 0; + if (numRead > 0) { + char *end = strchr(number, ']'); // check for end-of-array marker + bool foundDigit = (end == nullptr); // no ']' means the whole token is a number + if (end != nullptr) { + // ']' present — only accept if a digit (or '-') appears before it + for (int k = 0; &number[k] != end; k++) { + if (number[k] >= '0' && number[k] <= '9') { foundDigit = true; break; } + if (number[k] == '-') { foundDigit = true; break; } + } + } + if (!foundDigit) break; // ']' with no number — array ended + gapTable[gapIdx++] = constrain(atoi(number), -1, 1); + } else break; } + f.close(); + } + if (gapIdx < matrixSize) { // file was too short or could not be read — discard + p_free(gapTable); + gapTable = nullptr; } } DEBUG_PRINTLN(F("Gaps loaded.")); - releaseJSONBufferLock(); } unsigned x, y, pix=0; //pixel diff --git a/wled00/const.h b/wled00/const.h index e49dd2900a..e6601547ab 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -478,7 +478,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define JSON_LOCK_SERVEJSON 17 #define JSON_LOCK_NOTIFY 18 #define JSON_LOCK_PRESET_NAME 19 -#define JSON_LOCK_LEDGAP 20 +// JSON_LOCK 20 formerly used for LEDGAP (now parsed without JSON buffer) #define JSON_LOCK_LEDMAP_ENUM 21 #define JSON_LOCK_REMOTE 22 diff --git a/wled00/json.cpp b/wled00/json.cpp index 366c74e6d4..5b97c729f1 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1213,23 +1213,32 @@ static size_t writeJSONStringElement(uint8_t* dest, size_t maxLen, const char* s return 1 + n; } -// Generate a streamed JSON response for the mode data -// This uses sendChunked to send the reply in blocks based on how much fit in the outbound -// packet buffer, minimizing the required state (ie. just the next index to send). This -// allows us to send an arbitrarily large response without using any significant amount of -// memory (so no worries about buffer limits). -void respondModeData(AsyncWebServerRequest* request) { +// Generate a streamed JSON response for the mode data (namesOnly=false) or mode names +// (namesOnly=true). This uses sendChunked to send the reply in blocks based on how much +// fit in the outbound packet buffer, minimizing the required state (ie. just the next index +// to send). This allows us to send an arbitrarily large response without using any +// significant amount of memory (so no worries about buffer limits). +void respondModeData(AsyncWebServerRequest* request, bool namesOnly = false) { size_t fx_index = 0; request->sendChunked(FPSTR(CONTENT_TYPE_JSON), - [fx_index](uint8_t* data, size_t len, size_t) mutable { + [fx_index, namesOnly](uint8_t* data, size_t len, size_t) mutable { size_t bytes_written = 0; char lineBuffer[256]; while (fx_index < strip.getModeCount()) { strncpy_P(lineBuffer, strip.getModeData(fx_index), sizeof(lineBuffer)-1); // Copy to stack buffer for strchr if (lineBuffer[0] != 0) { lineBuffer[sizeof(lineBuffer)-1] = '\0'; // terminate string (only needed if strncpy filled the buffer) - const char* dataPtr = strchr(lineBuffer,'@'); // Find '@', if there is one - size_t mode_bytes = writeJSONStringElement(data, len, dataPtr ? dataPtr + 1 : ""); + char* dataPtr = strchr(lineBuffer,'@'); // Find '@', if there is one; non-const so namesOnly mode can truncate lineBuffer here + const char* value; + // namesOnly=true → emit the display name (everything before '@') + // namesOnly=false → emit the fx-data string (everything after '@') + if (namesOnly) { + if (dataPtr) *dataPtr = '\0'; // truncate at '@' to get name only + value = lineBuffer; + } else { + value = dataPtr ? dataPtr + 1 : ""; // everything after '@' is the fx data + } + size_t mode_bytes = writeJSONStringElement(data, len, value); if (mode_bytes == 0) break; // didn't fit; break loop and try again next packet if (fx_index == 0) *data = '['; data += mode_bytes; @@ -1276,7 +1285,7 @@ class LockedJsonResponse: public AsyncJsonResponse { void serveJson(AsyncWebServerRequest* request) { enum class json_target { - all, state, info, state_info, nodes, effects, palettes, networks, config, pins + all, state, info, state_info, nodes, palettes, networks, config, pins }; json_target subJson = json_target::all; @@ -1285,7 +1294,7 @@ void serveJson(AsyncWebServerRequest* request) 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("eff")) > 0) { respondModeData(request, true); return; } else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes; else if (url.indexOf(F("fxda")) > 0) { respondModeData(request); return; } else if (url.indexOf(F("net")) > 0) subJson = json_target::networks; @@ -1312,7 +1321,7 @@ 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_target::effects); // will clear and convert JsonDocument into JsonArray if necessary + LockedJsonResponse *response = new LockedJsonResponse(pDoc, false); // will clear JsonDocument JsonVariant lDoc = response->getRoot(); @@ -1326,8 +1335,6 @@ void serveJson(AsyncWebServerRequest* request) serializeNodes(lDoc); break; case json_target::palettes: serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break; - case json_target::effects: - serializeModeNames(lDoc); break; case json_target::networks: serializeNetworks(lDoc); break; case json_target::config: