Skip to content

Add trailing strip support for sparse ledmaps#5745

Open
DedeHai wants to merge 2 commits into
wled:mainfrom
DedeHai:ledmap_improvement
Open

Add trailing strip support for sparse ledmaps#5745
DedeHai wants to merge 2 commits into
wled:mainfrom
DedeHai:ledmap_improvement

Conversation

@DedeHai

@DedeHai DedeHai commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Rework on gap and ledmap build.

  • Added a helper function to read numbers from a json array directly from a file with better tolerance for whitespaces.
  • Gap file no longer needs the json buffer, it now also reads directly from the file.
  • ledmap is now generated in a two-pass manner: read the file and count the number of physically mapped pixels then allocate memory for the matrix + remaining physical pixels. Read the file again and fill the buffer

I also added a (brute-force) loop to map any unmapped physical pixels at the end but commented it out (also did not test that). Currently any unmapped pixel with stay unmapped (-1) - it really is a design choice whether pixels left unmapped should be added after the map or just left blank.

Tested both gap and ledmap and for the tests I did it works as expected (gap file, gap file with trailing pixels, full ledmap, full ledmap with trailing strip, sparse ledmap with trailing strip). There may be some edge cases I missed.

fixes #5529

Summary by CodeRabbit

  • New Features

    • Improved support for custom and sparse 2D LED mappings.
    • 2D gap maps can now be loaded more reliably from configuration files.
    • Mapping data supports larger layouts and trailing pixels.
  • Bug Fixes

    • Invalid or out-of-range mapping values are handled safely.
    • Incomplete gap-map files no longer leave partially applied mappings.
    • Invalid trailing segments and unsupported 2D coordinates are corrected or rejected.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

2D gap maps now load through direct file streaming. Custom LED-map parsing reallocates tables for sparse and trailing pixels, while segment validation uses the expanded mapped length and checks trailing 1D coordinates.

Changes

Custom mapping and gap loading

Layer / File(s) Summary
Streaming gap-map loading
wled00/file.cpp, wled00/fcn_declare.h, wled00/const.h, wled00/FX_2Dfcn.cpp
Adds file-based integer parsing and uses it to load constrained gap values directly from /2d-gaps.json, removing the associated JSON lock constant.
Custom LED-map sizing and validation
wled00/FX_fcn.cpp
Accounts for custom mapping size, rebuilds 1D and 2D mapping tables with invalid-entry handling, and validates trailing 1D segments against the expanded mapped length and default coordinates.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant setUpMatrix as WS2812FX::setUpMatrix
  participant filesystem as WLED_FS
  participant parser as readNextIntFromFile
  participant gapTable
  setUpMatrix->>filesystem: Open /2d-gaps.json
  setUpMatrix->>parser: Read matrix gap integers
  parser-->>setUpMatrix: Return parsed integer
  setUpMatrix->>gapTable: Store constrained gap value
  setUpMatrix->>filesystem: Close file
Loading

Possibly related PRs

  • wled/WLED#5693: Both changes modify WS2812FX::deserializeMap() handling of the JSON map array.

Suggested labels: effect

Suggested reviewers: softhack007, willmmiles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Clearly describes the main change: adding trailing strip support for sparse LED maps.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
wled00/file.cpp (1)

366-407: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid, well-guarded parser — consider adding unit tests.

Logic checks out: overflow-safe saturation, correct consume/leave semantics for ,/], and graceful handling of malformed trailing characters. Given this new parser now backs both gap-file and ledmap loading (wled00/FX_2Dfcn.cpp, wled00/FX_fcn.cpp), a focused unit test covering leading whitespace, negative numbers, overflow, and malformed trailing chars would guard against regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/file.cpp` around lines 366 - 407, Add focused unit tests for
readNextIntFromFile covering leading whitespace, negative values,
oversized-number saturation, and malformed trailing characters. Verify the
parser’s comma consumption and array-terminator behavior while exercising the
same loading paths used by gap files and ledmaps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wled00/FX_fcn.cpp`:
- Around line 2227-2251: The 1D ledmap loader uses an overly strict token match
that rejects whitespace-formatted map arrays. In the 1D setup branch, update the
f.find check guarding map parsing to search for the same "\"map\"" token used by
the 2D path, while leaving the subsequent integer parsing unchanged.

---

Nitpick comments:
In `@wled00/file.cpp`:
- Around line 366-407: Add focused unit tests for readNextIntFromFile covering
leading whitespace, negative values, oversized-number saturation, and malformed
trailing characters. Verify the parser’s comma consumption and array-terminator
behavior while exercising the same loading paths used by gap files and ledmaps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46594de5-ded4-453c-b01d-2dc48a61ce5e

📥 Commits

Reviewing files that changed from the base of the PR and between c3dabbc and bd94908.

📒 Files selected for processing (5)
  • wled00/FX_2Dfcn.cpp
  • wled00/FX_fcn.cpp
  • wled00/const.h
  • wled00/fcn_declare.h
  • wled00/file.cpp

Comment thread wled00/FX_fcn.cpp
Comment on lines +2227 to +2251
} else {
// 1D set-up: allocate strip length and fill with entries from file
// partial maps leave indices beyond customMappingSize unmapped (-1) TODO: see note above about appending unmapped pixels
const unsigned mapSize = getLengthTotal();
customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t) * mapSize)); // prefer DRAM for speed

if (customMappingTable) {
memset(customMappingTable, 0xFF, sizeof(uint16_t) * mapSize); // pre-fill with "-1" i.e. unmapped pixel
DEBUG_PRINTF_P(PSTR("ledmap allocated: %uB\n"), sizeof(uint16_t)*mapSize);
File f = WLED_FS.open(fileName, "r");
if (f && f.find("\"map\":[")) {
int value;
unsigned mapindex = 0;
while (mapindex < mapSize && readNextIntFromFile(f, value)) {
if (value < 0 || value >= _length) value = 0xFFFF; // prevent integer wrap around
customMappingTable[mapindex++] = (uint16_t)value;
}
customMappingSize = mapSize;
currentLedmap = n;
f.close();
}
} else {
DEBUG_PRINTLN(F("ERROR LED map allocation error."));
}
currentLedmap = n;
f.close();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the 2D branch's find() target and close() placement for comparison
rg -n 'f\.find\("\\"map' wled00/FX_fcn.cpp
rg -n 'f\.close\(\)' wled00/FX_fcn.cpp

Repository: wled/WLED

Length of output: 427


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant sections around both branches.
sed -n '2160,2255p' wled00/FX_fcn.cpp

printf '\n--- search for File/FS close patterns ---\n'
rg -n 'File f = WLED_FS\.open|\.close\(\); // all done, close the file|if \(f && f\.find\("\\"map"\\"\)\)' wled00 -g'*.cpp' -g'*.h'

printf '\n--- look for documentation about File destructor / close semantics ---\n'
rg -n 'destructor|close\(\)|File' docs wled00 -g'*.md' -g'*.cpp' -g'*.h' | sed -n '1,120p'

Repository: wled/WLED

Length of output: 15449


🌐 Web query:

ESP32 Arduino FS File destructor close semantics File destructor closes handle automatically

💡 Result:

In the ESP32 Arduino framework, the fs::File class uses reference counting to manage its underlying file handle [1]. When a File object goes out of scope, its internal shared pointer (_p) is destroyed [1][2]. The underlying implementation class (e.g., VFSFileImpl or similar) is designed to release its resources—including closing the file handle—when its own destructor runs [1][3]. Consequently, when the last File object referencing a specific handle is destroyed, the file is automatically closed [1]. While this automatic mechanism exists, it is widely considered best practice in embedded development to explicitly call File::close() when you are finished with a file [1][4]. Explicitly calling close() ensures that: 1. Any buffered data is immediately flushed to the storage medium. 2. The file handle and associated memory are released immediately, rather than waiting for the object to go out of scope or for the reference count to reach zero [4]. 3. You avoid potential issues related to delayed resource reclamation in memory-constrained environments [4]. In summary, while the destructor provides a safety mechanism for automatic closure [1], relying on it is not a substitute for explicit resource management via close() in robust application code [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- occurrences of the ledmap JSON key in the repo ---\n'
rg -n '"map"\s*:|\\"map\\":\[' wled00 docs -g'*.cpp' -g'*.h' -g'*.json' -g'*.md'

printf '\n--- surrounding code where ledmap files are written/serialized ---\n'
rg -n 'customMappingTable|customMappingSize|currentLedmap|ledmap|map\]' wled00 -g'*.cpp' -g'*.h' | sed -n '1,160p'

Repository: wled/WLED

Length of output: 238


🌐 Web query:

Arduino FS File destructor close() automatically scope exit ESP32 ESP8266

💡 Result:

In the ESP32 and ESP8266 Arduino cores, the fs::File object is designed to manage its own lifecycle [1][2][3]. When a File object goes out of scope, its destructor is invoked, which eventually triggers the closure of the underlying file handle [1][2]. For the ESP32, the File class uses a reference-counted pointer to the underlying implementation class (FileImpl) [1]. When the File object is destroyed, the reference count decreases, and the implementation’s destructor handles the actual closing of the file [1][4]. For the ESP8266, the underlying implementation similarly ensures that files are closed when the File object is destroyed [2][3]. While official documentation explicitly lists close as a method to call [5], and doing so manually is considered good practice for predictable resource management, the architectural design of these cores ensures that files are not left permanently open (leaked) when the object goes out of scope [1][6][2]. In summary, while you are not strictly required to call close to prevent leaks, it remains a best practice to call close manually if you need to ensure the file is closed at a specific point in your code, such as before further processing the data or to immediately free memory resources [6][3][5].

Citations:


Relax the 1D ledmap token match f.find("\"map\":[") is stricter than the 2D path and skips valid pretty-printed ledmap files with whitespace around the array; use the same f.find("\"map\"") check here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/FX_fcn.cpp` around lines 2227 - 2251, The 1D ledmap loader uses an
overly strict token match that rejects whitespace-formatted map arrays. In the
1D setup branch, update the f.find check guarding map parsing to search for the
same "\"map\"" token used by the 2D path, while leaving the subsequent integer
parsing unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sparse map followed by trailing strip is not supported

1 participant