Skip to content

Prepare merge from dev to main - #51

Merged
ewowi merged 76 commits into
mainfrom
dev
Apr 6, 2026
Merged

Prepare merge from dev to main#51
ewowi merged 76 commits into
mainfrom
dev

Conversation

@ewowi

@ewowi ewowi commented Feb 16, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Hardware sprite overlay, runtime driver reconfiguration, and a new demo sketch with built-in effects and target-specific pin setups.
  • Documentation

    • Full end-user and developer guides, MkDocs site, onboarding docs, and additional reference pages.
  • Improvements

    • Examples and README use explicit unsigned types; default strip count reduced for simpler getting-started.
  • Chores

    • CI workflows, linting and formatting configs, build/platform files, helper scripts, and tooling updates added.

Yves BAZIN and others added 30 commits July 4, 2024 16:48
- platformio.ini: set 54.03.20 platform and 4 boards (P4 not supported)
- clockless driver: #define TAG format
- add main.cpp
Setup pio support, add main, #define TAG format
exclude main.cpp and examples in library.json
- pio.ini: pioarduino update
- Driver: int *Pins -> uint8_t *Pins
pins array uint8_t for compatibility with other libraries
Results in Cache disabled but cached memory region accessed

(+ correction in main.cp(p))
Compile without warn, add idf 5.5 checks, remove IRAM_ATTR from forwards
This avoid compilation errors if compiled in C3 or P4 context
- prepares for P4 support
- functionality for D0 and S3 unchanged

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (1)
src/I2SClocklessLedDriver.cpp (1)

43-54: ⚠️ Potential issue | 🔴 Critical

Clear wasWaitingtofinish on the timeout return path.

Line 50 returns before resetting the flag. If the current transfer finishes later, i2sStop() will still post to waitDisp, leaving a stale token behind. The next updateDriver() can then consume that stale token, skip the real wait, and free DMA buffers while a new transfer is still active.

🛠️ Minimal fix
-    if (xSemaphoreTake(waitDisp, pdMS_TO_TICKS(500)) == pdFALSE) {
+    BaseType_t waitResult = xSemaphoreTake(waitDisp, pdMS_TO_TICKS(500));
+    wasWaitingtofinish = false;
+    if (waitResult == pdFALSE) {
       ESP_LOGE(TAG, "updateDriver: timeout waiting for DMA to idle, aborting reconfiguration");
       return;  // members unchanged — old DMA state remains consistent
     }
-    wasWaitingtofinish = false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/I2SClocklessLedDriver.cpp` around lines 43 - 54, The timeout return path
in updateDriver() leaves wasWaitingtofinish set true which can cause a stale
semaphore token to be consumed by a subsequent updateDriver(); modify the
timeout branch so that wasWaitingtofinish is cleared before returning (i.e., set
wasWaitingtofinish = false right before the return after the xSemaphoreTake
timeout), ensuring i2sStop() posts no stale token that a later updateDriver()
would consume; locate symbols updateDriver(), wasWaitingtofinish, waitDisp and
i2sStop() in the diff to apply this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/lint.yml:
- Around line 56-69: clang-tidy is being run with relative source paths so
diagnostics may be emitted as relative paths and not match the grep anchored to
"^$(pwd)/src/…"; update the clang-tidy invocation (the command invoking
clang-tidy and the file arguments like src/I2SClocklessLedDriver.cpp and
src/HardwareSprite.cpp) to use absolute paths $(pwd)/src/… (and ensure
--header-filter already uses $(pwd)/src/.*), and make the post-run checks around
the output variable more strict: capture clang-tidy's exit status separately
(instead of blindly using "|| true") and if output is empty or clang-tidy failed
without usable output, print an error and exit non-zero; keep the existing grep
pattern (grep -qE "^$(pwd)/src/.*\[[a-z].*\]$") to detect violations.

In `@docs/developer/developer.md`:
- Line 9: Update the table entry that currently references `src/pixeltypes.h` so
it points to the actual header used in the codebase (`src/___pixeltypes.h`);
find the table row containing the text "`src/pixeltypes.h` | `Pixel` struct and
`Pixels` container" and replace the file reference with the correct header name
so readers are directed to the actual pixel-types header.
- Around line 65-71: The docs currently reference a non-existent symbol
`I2SClocklessLedDriver_waitDisp`; update the documentation to use the actual
member name `waitDisp` everywhere (e.g., in the table and subsequent paragraphs)
so readers can grep and trace the semaphore flow correctly; ensure sentences
that describe its lazy creation and usage by `showPixels(NO_WAIT)`,
`waitDisplay()`, and `updateDriver()` refer to `waitDisp` and leave the
`wasWaitingtofinish` explanation unchanged.

In `@src/HardwareSprite.h`:
- Around line 77-83: The reorder() function writes into the global pointer
target without checking it; add a guard at the start of reorder (in the reorder
method) to return early if target == nullptr (in addition to existing
displaySprite and leds checks) so no writes to target[pixelOffset] occur when
the driver hasn't been initialized or has been deleted; ensure the guard covers
the entire nested loops that reference target and leave all existing logic
(offset(), spritenumber, NB_COMPONENTSS calculations) unchanged.

In `@src/helper.h`:
- Around line 5-31: The macros HOW_LONG, RUN_SKETCH_FOR, and RUN_SKETCH_N_TIMES
must be wrapped as a single-statement macro (use the do { ... } while (0)
pattern) so they behave correctly inside conditional statements; update each
macro (HOW_LONG, RUN_SKETCH_FOR, RUN_SKETCH_N_TIMES) to enclose their existing
block in do { ... } while (0) and preserve all internal variable names and
printf/logic exactly as-is to avoid changing behavior.

In `@src/pixeltypes.h`:
- Around line 133-142: The copy(Pixels ori) function is currently taking its
argument by value which triggers the Pixels copy-constructor that clears
mapFunction/arguments and thus breaks MAP-mode; change the copy() parameter to
take a reference (e.g., const Pixels& ori) so the original object's mapping
metadata is preserved when copy() iterates over ori (adjust any call sites if
needed), leaving the Pixels(const Pixels& rhs) constructor unchanged.
- Around line 171-188: initPixelsImpl currently sets pib->numStrips before
allocating ledpointer which can leave metadata populated when allocation fails;
move or clear pib->numStrips on failure and ensure consistent state by setting
pib->numStrips = 0 (and pib->pixelSize = 0) when calloc returns NULL. In the
createSubset() overloads, guard against null-pointer arithmetic by checking
ledpointer (the global/static buffer used) before doing expressions like
ledpointer + start and return an empty/invalid subset (or set subset->pixelSize
= 0 and subset->ledpointer = NULL) when ledpointer is NULL or start >=
pixelSize; use the functions/variables named createSubset, initPixelsImpl,
ledpointer, pixelSize, and numStrips to locate and apply these checks and state
updates.

In `@tools/patch_compile_db.py`:
- Around line 109-118: The current raw string replaces on template["command"]
can fail when paths are quoted; instead parse the command with shlex.split(),
find and replace the output object name "I2SClocklessLedDriver.cpp.o" and the
source token "src/I2SClocklessLedDriver.cpp" with BASE +
"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/src/HardwareSprite.cpp" (for the source token) and "HardwareSprite.cpp.o" (for
the output), then reassemble the command by quoting each token with
shlex.quote() and join with spaces; update the sprite dict's "command" to this
reconstructed command while keeping sprite["file"] = BASE +
"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/src/HardwareSprite.cpp" and base the changes off the existing template
variable.

---

Duplicate comments:
In `@src/I2SClocklessLedDriver.cpp`:
- Around line 43-54: The timeout return path in updateDriver() leaves
wasWaitingtofinish set true which can cause a stale semaphore token to be
consumed by a subsequent updateDriver(); modify the timeout branch so that
wasWaitingtofinish is cleared before returning (i.e., set wasWaitingtofinish =
false right before the return after the xSemaphoreTake timeout), ensuring
i2sStop() posts no stale token that a later updateDriver() would consume; locate
symbols updateDriver(), wasWaitingtofinish, waitDisp and i2sStop() in the diff
to apply this change.
🪄 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: 178d92df-6656-4ea6-8745-290901b982da

📥 Commits

Reviewing files that changed from the base of the PR and between edf82fa and 6196203.

📒 Files selected for processing (20)
  • .clang-tidy
  • .cppcheck-suppressions
  • .github/workflows/docs.yml
  • .github/workflows/lint.yml
  • .gitignore
  • CLAUDE.md
  • compile_commands.json
  • docs/developer/developer.md
  • docs/developer/standardsandguidelines.md
  • lint.sh
  • mkdocs.yml
  • src/HardwareSprite.cpp
  • src/HardwareSprite.h
  • src/I2SClocklessLedDriver.cpp
  • src/I2SClocklessLedDriver.h
  • src/framebuffer.h
  • src/helper.h
  • src/pixeltypes.h
  • tools/host_stubs/sys/reent.h
  • tools/patch_compile_db.py
✅ Files skipped from review due to trivial changes (8)
  • tools/host_stubs/sys/reent.h
  • .cppcheck-suppressions
  • .gitignore
  • .github/workflows/docs.yml
  • mkdocs.yml
  • docs/developer/standardsandguidelines.md
  • CLAUDE.md
  • .clang-tidy

Comment thread .github/workflows/lint.yml
Comment thread docs/developer/developer.md
Comment thread docs/developer/developer.md Outdated
Comment thread src/HardwareSprite.h Outdated
Comment thread src/helper.h
Comment thread src/pixeltypes.h
Comment thread src/pixeltypes.h
Comment thread tools/patch_compile_db.py Outdated
@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== .gitignore ==="
cat .gitignore

echo ""
echo "=== compile_commands.json tracked? ==="
git ls-files compile_commands.json

echo ""
echo "=== lint.sh ==="
cat lint.sh 2>/dev/null || echo "not found at root"
fd -g "lint.sh" --exec cat {}

echo ""
echo "=== .clang-tidy ==="
cat .clang-tidy 2>/dev/null || echo "not found"

echo ""
echo "=== GitHub Actions lint workflow ==="
cat .github/workflows/lint.yml 2>/dev/null || fd -g "lint.yml" --exec cat {}

echo ""
echo "=== platformio.ini build_flags ==="
grep -n "build_flags\|build_unflags\|clang\|tidy\|compile_commands" platformio.ini

echo ""
echo "=== tools/patch_compile_db.py ==="
cat tools/patch_compile_db.py 2>/dev/null || fd -g "patch_compile_db.py" --exec cat {}

Length of output: 14357


@ewowi — Noted that the namespace wrapper is out of scope. Here's the detailed analysis and proposed fixes for the remaining three items, plus the build error:


1. compile_commands.json tracked in git

Why it's a problem:
The file is in .gitignore but was committed before the gitignore entry was added — Git continues tracking already-staged files regardless of .gitignore. Two side-effects:

  • Any contributor cloning the repo gets a stale compile_commands.json with absolute paths from your machine, so lint.sh's if [ ! -f compile_commands.json ] guard never triggers regeneration
  • The file is a noisy generated artifact that changes on every build

Fix:

git rm --cached compile_commands.json
git commit -m "chore: stop tracking generated compile_commands.json"

The entry is already in .gitignore, so this is the only step needed.


2. Breaking API renames

The clang-tidy readability-identifier-naming pass renamed several public identifiers:

Old name New name Kind
frameBuffer FrameBuffer class
hardwareSprite HardwareSprite class
leddirection LedDirection enum
colorarrangment ColorArrangement enum
displayMode DisplayMode enum
OffsetDisplay::panel_width panelWidth member
OffsetDisplay::panel_height panelHeight member
Pixels::_size pixelSize member
Pixels::_num_strips numStrips member
Pixels::_direction direction member

These break any user code that accesses these by name. Proposed actions:

Option A (minimum friction): add C++14 deprecated type aliases right after the renamed class declarations in the relevant headers:

// Backward-compatibility aliases — will be removed in a future release
using frameBuffer  [[deprecated("Use FrameBuffer")]]  = FrameBuffer;
using hardwareSprite [[deprecated("Use HardwareSprite")]] = HardwareSprite;
using leddirection [[deprecated("Use LedDirection")]] = LedDirection;

Member renames (_sizepixelSize etc.) are harder to alias; those are best documented as breaking.

Option B (document only): Add a CHANGELOG.md or a note in README.md:

## Breaking changes (dev → main)
- `frameBuffer``FrameBuffer`
- `hardwareSprite``HardwareSprite`
- `leddirection``LedDirection`
- `colorarrangment``ColorArrangement`
- `displayMode``DisplayMode`
- `OffsetDisplay::panel_width``panelWidth`, `panel_height``panelHeight`
- `Pixels::_size``pixelSize`, `_num_strips``numStrips`, `_direction``direction`

Which option would you prefer?


3. Pixels copy-construct-but-no-assign

This is intentional and correct, but worth documenting. The semantics are:

  • Copy constructor creates a non-owning view: it copies the ledpointer and _sizes/dimensions but sets mapFunction = nullptr, arguments = nullptr, and localLedPointer = false. The copy doesn't own the pointer and won't free it on destruction.
  • Assignment operator = deleted prevents p1 = p2 assignment, which could silently create ambiguous ownership.

This models "view semantics on copy, no assignment". It's correct but worth documenting in a comment above the class or in the developer guide:

// Copy semantics: produces a non-owning view of the source ledpointer.
// The copy does NOT inherit mapFunction/arguments; use setMapFunction() separately.
// Assignment is deleted to prevent accidental shallow copies that may outlive the source.
inline Pixels(const Pixels& rhs) ...
Pixels& operator=(const Pixels&) = delete;

4. xthal_set_intset build error ⚠️

This is most likely caused by tools/patch_compile_db.py mutating your PlatformIO package installation. The script uses shutil.copy2 to copy xtensa base headers directly into:

~/.platformio/packages/framework-arduinoespressif32-libs/esp32s3/include/xtensa/...

Those copied headers then get picked up by the real pio run GCC compilation of framework files like SPI.cpp, pulling in xtensa_api.h which calls xthal_set_intset — a function not declared in that header context.

Immediate fix — restore the packages:

# Wipe the corrupted framework package and let PlatformIO re-download it
rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs
pio run -e esp32-s3-devkitc-1

Permanent fix — stop mutating system packages in patch_compile_db.py:

Replace the shutil.copy2 block that writes into chip_xtensa_dir with a write into a local stubs directory, then prepend that as an -isystem path:

# Instead of modifying the actual PlatformIO package dirs, copy stubs locally
host_stubs = os.path.join(OUTPUT_DIR, "xtensa_stubs")
os.makedirs(host_stubs, exist_ok=True)

for fname in os.listdir(base_xtensa):
    src = os.path.join(base_xtensa, fname)
    dst = os.path.join(host_stubs, fname)
    if os.path.isfile(src) and not os.path.exists(dst):
        print(f"Copying xtensa stub {fname}{dst}")
        shutil.copy2(src, dst)

And update the command injection to prepend the stubs dir instead:

# Replace the existing host_stubs injection
host_stubs_path = os.path.join(OUTPUT_DIR, "xtensa_stubs")
filtered.insert(1, "-isystem" + host_stubs_path)

This keeps the PlatformIO package tree clean and avoids the cross-contamination.


✏️ Learnings added
Learnt from: ewowi
URL: https://github.com/hpwit/I2SClocklessLedDriver/pull/51

Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51`, tools/patch_compile_db.py uses shutil.copy2 to copy xtensa base headers directly into the ~/.platformio/packages/framework-arduinoespressif32-libs/ package tree (in-place), which corrupts the PlatformIO installation and causes "xthal_set_intset was not declared in this scope" errors in subsequent pio run builds. Fix: rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs to restore clean packages. Permanent fix: copy stubs into OUTPUT_DIR/xtensa_stubs/ instead and use -isystem pointing there.

Learnt from: ewowi
URL: https://github.com/hpwit/I2SClocklessLedDriver/pull/51

Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51` (commit 77853ba), Pixels copy constructor creates a non-owning view (localLedPointer=false, mapFunction=nullptr, arguments=nullptr); assignment operator is deleted. This is intentional: copy semantics produce a view, not a deep copy. The namespace wrapper for Pixel/Pixels/leddirection is out of scope for this PR and deferred.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T18:21:16.152Z
Learning: In the I2SClocklessLedDriver library (hpwit/I2SClocklessLedDriver), the user (ewowi) confirmed that the following are correctly implemented as of PR `#51` / commit 450d5c1: getStrip() bounds guard, getLengths() uint16_t* return type, createSubset clamping, frameBuffer null-deref protection with _offPixel sentinel and valid(), hardwareSprite NBSPRITE overflow guard, deleteDriver() nulling DMABuffersTampon, freeing target and _hmap, ISR semaphore logic, and main.cpp effect cycling. The namespace wrapper for struct Pixel / class Pixels / enum class leddirection in src/___pixeltypes.h is intentionally deferred as a missing feature (not a bug).

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 51
File: src/main.cpp:1-2
Timestamp: 2026-04-05T18:39:10.248Z
Learning: In hpwit/I2SClocklessLedDriver, `src/main.cpp` is the standalone demo entrypoint guarded by `#ifdef PLATFORM_VERSION`. It is safely excluded from library consumers by two independent mechanisms: (1) `library.json` `build.srcFilter` explicitly excludes `main.cpp` with `"-<main.cpp>"`, and (2) `PLATFORM_VERSION` is only defined within the PlatformIO dev environments in `platformio.ini` and is never set for library consumers. No rename or additional guard is needed.

@ewowi

ewowi commented Apr 5, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai, regarding

Permanent fix — stop mutating system packages in patch_compile_db.py

Can you show exactly what changes need to be made: what to remove (-) and what to add (+)

@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me read the file to generate an exact diff.


🏁 Script executed:

#!/bin/bash
cat tools/patch_compile_db.py

Length of output: 6010


@ewowi — Here are the three exact changes needed in tools/patch_compile_db.py:


Change 1 — define a local stubs output path (near line 57, after BASE =)

 BASE = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+XTENSA_STUBS = os.path.abspath(os.path.join(OUTPUT_DIR, "xtensa_stubs"))

Change 2 — inject that path as -isystem in the compile command (after host_stubs injection, ~line 96)

     # Prepend a stub isystem path for newlib/platform headers missing on macOS.
     host_stubs = BASE + "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/tools/host_stubs"
     filtered.insert(1, "-isystem" + host_stubs)
+    filtered.insert(2, "-isystem" + XTENSA_STUBS)

Change 3 — stop writing into the PlatformIO package tree; write into XTENSA_STUBS instead (the loop at the bottom of the file)

-        chip_xtensa_dir = os.path.join(inc_path, "xtensa")
-        # Look for the sibling base xtensa dir: walk up to the xtensa/ root,
+        # Look for the sibling base xtensa dir: walk up to the xtensa/ root,
         # then look for include/xtensa/ next to the chip-specific tree.
         # Pattern: .../xtensa/<chip>/include  →  sibling: .../xtensa/include/xtensa/
         parent = os.path.dirname(os.path.dirname(inc_path))  # strip /<chip>/include
         base_xtensa = os.path.join(parent, "include", "xtensa")
         if not os.path.isdir(base_xtensa):
             continue
+        os.makedirs(XTENSA_STUBS, exist_ok=True)
         for fname in os.listdir(base_xtensa):
             src = os.path.join(base_xtensa, fname)
-            dst = os.path.join(chip_xtensa_dir, fname)
+            dst = os.path.join(XTENSA_STUBS, fname)
             if os.path.isfile(src) and not os.path.exists(dst):
                 print(f"Copying xtensa stub {fname} → {dst}")
                 shutil.copy2(src, dst)

Why this works

Before After
Stubs copied into ~/.platformio/…/xtensa/ Stubs copied into .lint_db/xtensa_stubs/
PlatformIO package tree mutated → pio run picks up foreign headers → xthal_set_intset error PlatformIO tree untouched; stubs only visible to clang-tidy via -isystem
clang-tidy found stubs because they were on the existing -I search path clang-tidy finds stubs via the new -isystem .lint_db/xtensa_stubs flag injected in Change 2

The XTENSA_STUBS constant is defined at module level so both the injection (Change 2) and the copy (Change 3) reference the same path without duplication.


🧠 Learnings used
Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T18:21:16.152Z
Learning: In the I2SClocklessLedDriver library (hpwit/I2SClocklessLedDriver), the user (ewowi) confirmed that the following are correctly implemented as of PR `#51` / commit 450d5c1: getStrip() bounds guard, getLengths() uint16_t* return type, createSubset clamping, frameBuffer null-deref protection with _offPixel sentinel and valid(), hardwareSprite NBSPRITE overflow guard, deleteDriver() nulling DMABuffersTampon, freeing target and _hmap, ISR semaphore logic, and main.cpp effect cycling. The namespace wrapper for struct Pixel / class Pixels / enum class leddirection in src/___pixeltypes.h is intentionally deferred as a missing feature (not a bug).

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51` (commit 77853ba), Pixels copy constructor creates a non-owning view (localLedPointer=false, mapFunction=nullptr, arguments=nullptr); assignment operator is deleted. This is intentional: copy semantics produce a view, not a deep copy. The namespace wrapper for Pixel/Pixels/leddirection is out of scope for this PR and deferred.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51`, tools/patch_compile_db.py uses shutil.copy2 to copy xtensa base headers directly into the ~/.platformio/packages/framework-arduinoespressif32-libs/ package tree (in-place), which corrupts the PlatformIO installation and causes "xthal_set_intset was not declared in this scope" errors in subsequent pio run builds. Fix: rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs to restore clean packages. Permanent fix: copy stubs into OUTPUT_DIR/xtensa_stubs/ instead and use -isystem pointing there.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 51
File: src/main.cpp:1-2
Timestamp: 2026-04-05T18:39:10.248Z
Learning: In hpwit/I2SClocklessLedDriver, `src/main.cpp` is the standalone demo entrypoint guarded by `#ifdef PLATFORM_VERSION`. It is safely excluded from library consumers by two independent mechanisms: (1) `library.json` `build.srcFilter` explicitly excludes `main.cpp` with `"-<main.cpp>"`, and (2) `PLATFORM_VERSION` is only defined within the PlatformIO dev environments in `platformio.ini` and is never set for library consumers. No rename or additional guard is needed.

@ewowi

ewowi commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai, ets_delay_us(16); // for sk6812 gave a compile error, can I safely replace with esp_rom_delay_us(16); // for sk6812, for different esp_idf versions and board targets?

@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ewowi — Yes, esp_rom_delay_us is the correct drop-in replacement for ets_delay_us and it is safe across all three of your targets. Here's the full picture:


Why ets_delay_us broke

ets_delay_us was previously defined in rom/ets_sys.h, but in ESP-IDF 5 it no longer resolves from the default include path, causing the 'ets_delay_us' was not declared in this scope compile error you hit.

Why esp_rom_delay_us is the right replacement

esp_rom_delay_us is available by including esp_rom_sys.h. It is the modern ESP-IDF-provided wrapper and works on:

  • ESP32 (Xtensa LX6)
  • ESP32-S3 (Xtensa LX7)
  • ESP32-P4 — the ESP32-P4 is powered by a dual-core RISC-V CPU, so it has no Xtensa ROM. ets_delay_us is purely an Xtensa ROM symbol and would fail to link there anyway. esp_rom_delay_us resolves via the esp_rom IDF component, which has a RISC-V implementation for P4.

One caveat

esp_rom_delay_us is marked as "Internal and Unstable" in Espressif's official API docs. You should call the CPU frequency update function when the CPU frequency is changed, otherwise esp_rom_delay_us can be inaccurate. For a fixed 16 µs reset/latch delay on SK6812, this is not a concern in practice since the CPU frequency doesn't change during LED output.


Required change

-#include "rom/ets_sys.h"   // or wherever ets_delay_us was pulled in
+#include "esp_rom_sys.h"

-ets_delay_us(16);  // for sk6812
+esp_rom_delay_us(16);  // for sk6812

This is a one-line change per call site and is what major Arduino/IDF libraries (e.g. LovyanGFX) adopted when migrating to ESP-IDF 5.


🧠 Learnings used
Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T18:21:16.152Z
Learning: In the I2SClocklessLedDriver library (hpwit/I2SClocklessLedDriver), the user (ewowi) confirmed that the following are correctly implemented as of PR `#51` / commit 450d5c1: getStrip() bounds guard, getLengths() uint16_t* return type, createSubset clamping, frameBuffer null-deref protection with _offPixel sentinel and valid(), hardwareSprite NBSPRITE overflow guard, deleteDriver() nulling DMABuffersTampon, freeing target and _hmap, ISR semaphore logic, and main.cpp effect cycling. The namespace wrapper for struct Pixel / class Pixels / enum class leddirection in src/___pixeltypes.h is intentionally deferred as a missing feature (not a bug).

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51` (commit 77853ba), Pixels copy constructor creates a non-owning view (localLedPointer=false, mapFunction=nullptr, arguments=nullptr); assignment operator is deleted. This is intentional: copy semantics produce a view, not a deep copy. The namespace wrapper for Pixel/Pixels/leddirection is out of scope for this PR and deferred.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51`, tools/patch_compile_db.py uses shutil.copy2 to copy xtensa base headers directly into the ~/.platformio/packages/framework-arduinoespressif32-libs/ package tree (in-place), which corrupts the PlatformIO installation and causes "xthal_set_intset was not declared in this scope" errors in subsequent pio run builds. Fix: rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs to restore clean packages. Permanent fix: copy stubs into OUTPUT_DIR/xtensa_stubs/ instead and use -isystem pointing there.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 51
File: src/main.cpp:1-2
Timestamp: 2026-04-05T18:39:10.248Z
Learning: In hpwit/I2SClocklessLedDriver, `src/main.cpp` is the standalone demo entrypoint guarded by `#ifdef PLATFORM_VERSION`. It is safely excluded from library consumers by two independent mechanisms: (1) `library.json` `build.srcFilter` explicitly excludes `main.cpp` with `"-<main.cpp>"`, and (2) `PLATFORM_VERSION` is only defined within the PlatformIO dev environments in `platformio.ini` and is never set for library consumers. No rename or additional guard is needed.

- guard framebuffer and pixeltypes with #ifdef USE_PIXELSLIB
- guard HardwareSprite with #if HARDWARESPRITES == 1
- clocklessdriver guard Pixel

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/HardwareSprite.h (2)

1-1: Consider adding a fallback definition for HARDWARESPRITES.

This header is entirely guarded by #if HARDWARESPRITES == 1, but the macro is only defaulted in src/I2SClocklessLedDriver.h (lines 153-157). If a user includes HardwareSprite.h directly without first including the driver header, the macro will be undefined and the entire file will be silently skipped—or worse, cause a preprocessor error depending on the compiler's treatment of undefined macros in #if directives.

Suggested defensive guard
+#ifndef HARDWARESPRITES
+  `#define` HARDWARESPRITES 0
+#endif
 `#if` HARDWARESPRITES == 1
 `#include` "FastLED.h"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareSprite.h` at line 1, The header is fully guarded by "#if
HARDWARESPRITES == 1" but HARDWARESPRITES may be undefined if HardwareSprite.h
is included standalone; add a defensive fallback by ensuring HARDWARESPRITES has
a default (e.g., wrap the existing check with an `#ifndef` HARDWARESPRITES /
`#define` HARDWARESPRITES 0 or change the guard to use defined(HARDWARESPRITES)
before comparing) so the content is not silently skipped or misprocessed; update
the top of HardwareSprite.h to define HARDWARESPRITES to 0 when undefined or use
"defined(HARDWARESPRITES) && HARDWARESPRITES == 1" in the conditional to
preserve current behavior.

78-94: Bounds check assumes caller provides correct dimensions.

The bounds check pixelOffset < width * height validates against the caller-provided dimensions, but target is allocated in I2SClocklessLedDriver.h as numLedPerStrip * numStrips elements. If reorder() is called with dimensions that don't match the driver's actual LED count, the bounds check passes but the write could overflow the buffer.

This is acceptable if reorder() is only called internally with known-correct values, but consider documenting this precondition in the class docstring or adding an assertion in debug builds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareSprite.h` around lines 78 - 94, reorder() currently bounds-checks
pixelOffset against the caller-provided width*height but has no guarantee that
that matches the actual size of the target buffer; add a defensive check or
debug assertion at the start of reorder() that validates width * height equals
(or is <=) the actual target buffer size before writing to target (e.g., compare
to an existing class member that stores the driver's LED count or add a new
expectedTargetSize/numLeds member), and fail early (assert or return) if the
sizes differ; reference symbols: reorder, target, width, height, pixelOffset,
leds, transparentColor, spritenumber, NB_COMPONENTSS.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/developer/standardsandguidelines.md`:
- Around line 40-42: Update the directory paths referenced in the two prompt
rules so they match the repo’s actual folders: change "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/docs/endusers" to
"docs/enduser" in the "End-user docs prompt" rule and change "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/docs/developers"
to "docs/developer" in the "Developer docs prompt" rule; edit the text under the
"End-user docs prompt" and "Developer docs prompt" headings in
standardsandguidelines.md to use the singular paths exactly as they exist in the
repository.

In `@tools/patch_compile_db.py`:
- Around line 145-165: The code is copying xtensa base headers directly into the
PlatformIO package tree (chip_xtensa_dir), which mutates system packages;
instead create a local stubs directory (e.g., OUTPUT_DIR/xtensa_stubs) and copy
files from base_xtensa into that local directory rather than into
chip_xtensa_dir (stop writing into the PlatformIO package). After creating
XTENSA_STUBS in the local output, inject an -isystem entry pointing to
os.path.dirname(XTENSA_STUBS) into the patched/filtered compile command so the
compiler picks up the stubs (modify the logic that builds the filtered command
to add the -isystem flag), and remove any code that writes into chip_xtensa_dir
or the PlatformIO tree (refer to
variables/inc_paths/core_h/chip_xtensa_dir/base_xtensa and the code that builds
the filtered command).

---

Nitpick comments:
In `@src/HardwareSprite.h`:
- Line 1: The header is fully guarded by "#if HARDWARESPRITES == 1" but
HARDWARESPRITES may be undefined if HardwareSprite.h is included standalone; add
a defensive fallback by ensuring HARDWARESPRITES has a default (e.g., wrap the
existing check with an `#ifndef` HARDWARESPRITES / `#define` HARDWARESPRITES 0 or
change the guard to use defined(HARDWARESPRITES) before comparing) so the
content is not silently skipped or misprocessed; update the top of
HardwareSprite.h to define HARDWARESPRITES to 0 when undefined or use
"defined(HARDWARESPRITES) && HARDWARESPRITES == 1" in the conditional to
preserve current behavior.
- Around line 78-94: reorder() currently bounds-checks pixelOffset against the
caller-provided width*height but has no guarantee that that matches the actual
size of the target buffer; add a defensive check or debug assertion at the start
of reorder() that validates width * height equals (or is <=) the actual target
buffer size before writing to target (e.g., compare to an existing class member
that stores the driver's LED count or add a new expectedTargetSize/numLeds
member), and fail early (assert or return) if the sizes differ; reference
symbols: reorder, target, width, height, pixelOffset, leds, transparentColor,
spritenumber, NB_COMPONENTSS.
🪄 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: 05b1a7e1-19f4-4e6d-9d9c-bd29eb07a358

📥 Commits

Reviewing files that changed from the base of the PR and between 6196203 and 86be1bc.

📒 Files selected for processing (11)
  • .github/workflows/lint.yml
  • CLAUDE.md
  • docs/developer/developer.md
  • docs/developer/standardsandguidelines.md
  • src/HardwareSprite.cpp
  • src/HardwareSprite.h
  • src/I2SClocklessLedDriver.h
  • src/framebuffer.h
  • src/helper.h
  • src/pixeltypes.h
  • tools/patch_compile_db.py
✅ Files skipped from review due to trivial changes (1)
  • src/HardwareSprite.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/helper.h
  • CLAUDE.md
  • src/framebuffer.h

Comment thread docs/developer/standardsandguidelines.md Outdated
Comment thread tools/patch_compile_db.py
@ewowi

ewowi commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai,

Lint / clang-tidy (push)Failing after 45s

see error:

0s
3s
Run # Cross-compilation framework headers (xtensa, newlib, RISC-V) cause fatal

Cross-compilation framework headers (xtensa, newlib, RISC-V) cause fatal

errors on host clang; we only fail if check violations appear in our src/.

output=$(clang-tidy
-p .lint_db
--header-filter="$(pwd)/src/."
"$(pwd)/src/I2SClocklessLedDriver.cpp"
"$(pwd)/src/HardwareSprite.cpp"
2>&1)
clang_exit=$?
echo "$output"
if [ -z "$output" ] && [ $clang_exit -ne 0 ]; then
echo "clang-tidy failed with exit code $clang_exit but produced no output."
exit 1
fi
if echo "$output" | grep -qE "^$(pwd)/src/.
[[a-z].*]$"; then
echo "clang-tidy found violations in project source files."
exit 1
fi
shell: /usr/bin/bash -e {0}
Error: Process completed with exit code 1.

$(pwd) was added based on your review, is that now the problem?

@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me pull the current workflow file and the actual CI job output to pinpoint the failure.


🏁 Script executed:

#!/bin/bash
echo "=== .github/workflows/lint.yml ==="
cat .github/workflows/lint.yml

echo ""
echo "=== lint.sh ==="
cat lint.sh 2>/dev/null || echo "(not found)"

echo ""
echo "=== Latest clang-tidy job run log (last 150 lines) ==="
gh run list --repo hpwit/I2SClocklessLedDriver --workflow=lint.yml --limit 3 --json databaseId,status,conclusion,createdAt | python3 -c "
import json, sys
runs = json.load(sys.stdin)
for r in runs:
    print(r)
"

# Get the specific run ID from the URL provided: 24025190553
gh run view 24025190553 --repo hpwit/I2SClocklessLedDriver --log 2>/dev/null | tail -200 || \
  gh api "repos/hpwit/I2SClocklessLedDriver/actions/runs/24025190553/jobs" --jq '.jobs[] | {id:.id, name:.name, conclusion:.conclusion}' 2>/dev/null

echo ""
echo "=== clang-tidy job steps log ==="
gh api "repos/hpwit/I2SClocklessLedDriver/actions/jobs/70062049549/logs" 2>/dev/null | tail -200 || echo "(log fetch failed)"

Length of output: 43609


@ewowi$(pwd) is not the problem. The root cause is a bash -e interaction with command substitution.


Root cause

GitHub Actions runs every run: block with bash -e (i.e., set -e). In bash, when a command substitution appears on the right-hand side of a variable assignment:

output=$(clang-tidy ...)

the exit status of the subshell is propagated to the assignment statement itself. Because clang-tidy always exits non-zero on this cross-compilation build (due to xtensa/newlib header errors), bash -e terminates the script immediately — before clang_exit=$? is reached, before echo "$output" runs, and before either if block is entered. That's why the log jumps straight from ##[endgroup] to ##[error]Process completed with exit code 1. with no output in between.

lint.sh already handles this correctly with || true:

clang_tidy_output=$(
  "$CLANG_TIDY" ...
  2>&1
) || true   # cross-compilation framework errors cause non-zero exit; checked below

lint.yml is missing that || true.


Fix — exact diff for .github/workflows/lint.yml

       - name: Run clang-tidy
         run: |
           # Cross-compilation framework headers (xtensa, newlib, RISC-V) cause fatal
           # errors on host clang; we only fail if check violations appear in our src/.
           output=$(clang-tidy \
             -p .lint_db \
             --header-filter="$(pwd)/src/.*" \
             "$(pwd)/src/I2SClocklessLedDriver.cpp" \
             "$(pwd)/src/HardwareSprite.cpp" \
-            2>&1)
-          clang_exit=$?
-          echo "$output"
-          if [ -z "$output" ] && [ $clang_exit -ne 0 ]; then
-            echo "clang-tidy failed with exit code $clang_exit but produced no output."
-            exit 1
-          fi
+            2>&1) || true
+          echo "$output"
           if echo "$output" | grep -qE "^$(pwd)/src/.*\[[a-z].*\]$"; then
             echo "clang-tidy found violations in project source files."
             exit 1
           fi

Why the secondary check is also removed: once || true is appended, clang_exit is always 0 (it captures the exit status of true, not of clang-tidy), so [ $clang_exit -ne 0 ] would never be true. Removing that dead block keeps things clean and matches lint.sh.


🧠 Learnings used
Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T18:21:16.152Z
Learning: In the I2SClocklessLedDriver library (hpwit/I2SClocklessLedDriver), the user (ewowi) confirmed that the following are correctly implemented as of PR `#51` / commit 450d5c1: getStrip() bounds guard, getLengths() uint16_t* return type, createSubset clamping, frameBuffer null-deref protection with _offPixel sentinel and valid(), hardwareSprite NBSPRITE overflow guard, deleteDriver() nulling DMABuffersTampon, freeing target and _hmap, ISR semaphore logic, and main.cpp effect cycling. The namespace wrapper for struct Pixel / class Pixels / enum class leddirection in src/___pixeltypes.h is intentionally deferred as a missing feature (not a bug).

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51` (commit 77853ba), Pixels copy constructor creates a non-owning view (localLedPointer=false, mapFunction=nullptr, arguments=nullptr); assignment operator is deleted. This is intentional: copy semantics produce a view, not a deep copy. The namespace wrapper for Pixel/Pixels/leddirection is out of scope for this PR and deferred.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51`, tools/patch_compile_db.py uses shutil.copy2 to copy xtensa base headers directly into the ~/.platformio/packages/framework-arduinoespressif32-libs/ package tree (in-place), which corrupts the PlatformIO installation and causes "xthal_set_intset was not declared in this scope" errors in subsequent pio run builds. Fix: rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs to restore clean packages. Permanent fix: copy stubs into OUTPUT_DIR/xtensa_stubs/ instead and use -isystem pointing there.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 51
File: src/main.cpp:1-2
Timestamp: 2026-04-05T18:39:10.248Z
Learning: In hpwit/I2SClocklessLedDriver, `src/main.cpp` is the standalone demo entrypoint guarded by `#ifdef PLATFORM_VERSION`. It is safely excluded from library consumers by two independent mechanisms: (1) `library.json` `build.srcFilter` explicitly excludes `main.cpp` with `"-<main.cpp>"`, and (2) `PLATFORM_VERSION` is only defined within the PlatformIO dev environments in `platformio.ini` and is never set for library consumers. No rename or additional guard is needed.

@ewowi

ewowi commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai, regarding

78-94: Bounds check assumes caller provides correct dimensions.

The existing bounds check on line 87 (pixelOffset >= 0 && pixelOffset < width * height) already prevents out-of-bounds writes within the width*height range. The deeper concern in the finding is: what if the actual
target buffer is smaller than width * height?

The problem: target is a bare extern uint16_t* — no size is stored anywhere in the class or globally. There's no existing member to validate against. To add this check you'd need to either:

  1. Add a targetSize member to HardwareSprite that callers must set (but a buggy caller could set it wrong too), or
  2. Add a targetSize parameter to reorder() (changes the API).

Neither option actually prevents the mismatch — they just shift where the caller has to be correct. The finding is a valid concern in theory, but there's nothing in the current code to validate against, so the fix
would add new API surface without meaningful safety gain.

I'd skip this one unless you want to change the reorder() signature to take a targetSize parameter. Do you want that?

@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ewowi — Your analysis is exactly right, and I'd recommend skipping the API change.

Both options shift the problem rather than solve it:

  • A targetSize member can be set incorrectly.
  • A targetSize parameter to reorder() can be passed incorrectly.

The only caller that knows the real allocation size is the code that called malloc/calloc for target in the first place — and that's already in I2SClocklessLedDriver. Since target is shared state set externally, the invariant "caller must allocate target to at least width * height elements" is a precondition that can't be enforced from inside HardwareSprite without a larger refactor.

The lightweight alternative to neither ignore it nor change the API is to document the precondition directly in the code:

 // Composites this sprite into the target buffer.
+// Precondition: `target` must point to a buffer of at least `width * height`
+// uint16_t elements. This is the caller's responsibility; no size validation
+// is performed here since `target` carries no associated size metadata.
 void reorder(int width, int height) {

That way the constraint is visible to future maintainers without touching the API or adding false safety. I'd leave it at that.


🧠 Learnings used
Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T18:21:16.152Z
Learning: In the I2SClocklessLedDriver library (hpwit/I2SClocklessLedDriver), the user (ewowi) confirmed that the following are correctly implemented as of PR `#51` / commit 450d5c1: getStrip() bounds guard, getLengths() uint16_t* return type, createSubset clamping, frameBuffer null-deref protection with _offPixel sentinel and valid(), hardwareSprite NBSPRITE overflow guard, deleteDriver() nulling DMABuffersTampon, freeing target and _hmap, ISR semaphore logic, and main.cpp effect cycling. The namespace wrapper for struct Pixel / class Pixels / enum class leddirection in src/___pixeltypes.h is intentionally deferred as a missing feature (not a bug).

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51` (commit 77853ba), Pixels copy constructor creates a non-owning view (localLedPointer=false, mapFunction=nullptr, arguments=nullptr); assignment operator is deleted. This is intentional: copy semantics produce a view, not a deep copy. The namespace wrapper for Pixel/Pixels/leddirection is out of scope for this PR and deferred.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51`, tools/patch_compile_db.py uses shutil.copy2 to copy xtensa base headers directly into the ~/.platformio/packages/framework-arduinoespressif32-libs/ package tree (in-place), which corrupts the PlatformIO installation and causes "xthal_set_intset was not declared in this scope" errors in subsequent pio run builds. Fix: rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs to restore clean packages. Permanent fix: copy stubs into OUTPUT_DIR/xtensa_stubs/ instead and use -isystem pointing there.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 51
File: src/main.cpp:1-2
Timestamp: 2026-04-05T18:39:10.248Z
Learning: In hpwit/I2SClocklessLedDriver, `src/main.cpp` is the standalone demo entrypoint guarded by `#ifdef PLATFORM_VERSION`. It is safely excluded from library consumers by two independent mechanisms: (1) `library.json` `build.srcFilter` explicitly excludes `main.cpp` with `"-<main.cpp>"`, and (2) `PLATFORM_VERSION` is only defined within the PlatformIO dev environments in `platformio.ini` and is never set for library consumers. No rename or additional guard is needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/HardwareSprite.h (1)

86-102: Consider hoisting width * height computation outside the nested loop.

The expression width * height is computed on every iteration (potentially SPRITE_WIDTH * SPRITE_HEIGHT times). While compilers often optimize this, explicitly hoisting it improves clarity and guarantees the optimization.

♻️ Suggested refactor
   void reorder(int width, int height) {
     if (displaySprite && leds != nullptr && target != nullptr) {
+      const int bufferSize = width * height;
       for (int i = 0; i < SPRITE_WIDTH; i++) {
         for (int j = 0; j < SPRITE_HEIGHT; j++) {
           if (leds[j * SPRITE_WIDTH + i] != transparentColor) {
             int pixelOffset = offset(i, j, width, height);
-            if (pixelOffset >= 0 && pixelOffset < width * height) target[pixelOffset] = (uint16_t)(((j * SPRITE_WIDTH + i) + spritenumber * SPRITE_WIDTH * SPRITE_HEIGHT) * NB_COMPONENTSS + 1);
+            if (pixelOffset >= 0 && pixelOffset < bufferSize) target[pixelOffset] = (uint16_t)(((j * SPRITE_WIDTH + i) + spritenumber * SPRITE_WIDTH * SPRITE_HEIGHT) * NB_COMPONENTSS + 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/HardwareSprite.h` around lines 86 - 102, The nested loops in reorder(int
width, int height) repeatedly compute width * height; hoist this into a local
variable (e.g., int area = width * height) before the for-loops and use area in
the bounds check instead of recomputing width * height each iteration; update
the conditional that uses pixelOffset (currently: if (pixelOffset >= 0 &&
pixelOffset < width * height)) to use the new area variable so bounds checks
against target are clearer and more efficient.
docs/developer/standardsandguidelines.md (1)

30-30: Clarify the AI review reference.

The emoji and parenthetical "(see AI)" are unclear. Specify "CodeRabbit AI" or provide a clearer reference.

✏️ Suggested clarification
-The 🐰 (see AI) will review each commit, please process the review recommendations.
+CodeRabbit AI will review each commit; please address the review recommendations.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/developer/standardsandguidelines.md` at line 30, Replace the ambiguous
"🐰 (see AI)" reference in the sentence "The 🐰 (see AI) will review each
commit, please process the review recommendations." with a clear name like
"CodeRabbit AI" (or another explicit AI reviewer name) so readers know which
tool is meant; update the text to read e.g. "The CodeRabbit AI will review each
commit; please process the review recommendations." and ensure the unique symbol
"🐰 (see AI)" is removed or annotated with "CodeRabbit AI" wherever it appears
in this document.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/developer/standardsandguidelines.md`:
- Line 42: The documentation text currently references the path
'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/docs/developer' which incorrectly implies an absolute filesystem path; update
the string to 'docs/developer' wherever it appears in the document (replace
'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/docs/developer' with 'docs/developer') so the example uses the correct
relative repository path.
- Line 28: The external markdown link "How to properly submit a PR"
(https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR) references
the unrelated WLED project; update the entry in
docs/developer/standardsandguidelines.md by either replacing that URL with a
repository-specific contributor/PR guide (or an internal CONTRIBUTING.md link)
or remove the entire link line if no equivalent resource exists, ensuring the
displayed link text and URL are changed together so the doc no longer points to
WLED.
- Line 40: Update the documentation text under the "End-user docs prompt"
heading by replacing the leading-slash path `/docs/enduser` with the relative
path `docs/enduser` wherever it appears (search for the string `/docs/enduser`
in the "End-user docs prompt" rule) so the example matches repository
conventions and does not imply an absolute filesystem path.

In `@src/HardwareSprite.h`:
- Around line 1-5: This header lacks a protective include guard: wrap the entire
file contents with a conventional guard (e.g., add `#ifndef HARDWARESPRITE_H` /
`#define HARDWARESPRITE_H` at the very top and the matching `#endif` at the end)
or add `#pragma once` as the first line, ensuring you place it before the
existing `#ifndef HARDWARESPRITES` / `#define HARDWARESPRITES` feature block so
the `HARDWARESPRITES` feature macro and the class/extern declarations are not
duplicated on multiple includes.

---

Nitpick comments:
In `@docs/developer/standardsandguidelines.md`:
- Line 30: Replace the ambiguous "🐰 (see AI)" reference in the sentence "The 🐰
(see AI) will review each commit, please process the review recommendations."
with a clear name like "CodeRabbit AI" (or another explicit AI reviewer name) so
readers know which tool is meant; update the text to read e.g. "The CodeRabbit
AI will review each commit; please process the review recommendations." and
ensure the unique symbol "🐰 (see AI)" is removed or annotated with "CodeRabbit
AI" wherever it appears in this document.

In `@src/HardwareSprite.h`:
- Around line 86-102: The nested loops in reorder(int width, int height)
repeatedly compute width * height; hoist this into a local variable (e.g., int
area = width * height) before the for-loops and use area in the bounds check
instead of recomputing width * height each iteration; update the conditional
that uses pixelOffset (currently: if (pixelOffset >= 0 && pixelOffset < width *
height)) to use the new area variable so bounds checks against target are
clearer and more efficient.
🪄 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: 8c70cd7b-7bd4-423a-8ed8-8bc32adf3ad0

📥 Commits

Reviewing files that changed from the base of the PR and between 86be1bc and e628e29.

📒 Files selected for processing (3)
  • .github/workflows/lint.yml
  • docs/developer/standardsandguidelines.md
  • src/HardwareSprite.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/lint.yml

Comment thread docs/developer/standardsandguidelines.md Outdated
Comment thread docs/developer/standardsandguidelines.md Outdated
Comment thread docs/developer/standardsandguidelines.md Outdated
Comment thread src/HardwareSprite.h

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
docs/developer/standardsandguidelines.md (1)

24-27: Consider slightly more formal tone in contributor standards.

“pretty please” reads informal for a standards/guidelines page. Consider neutral wording to keep the section consistent with the rest of the document.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/developer/standardsandguidelines.md` around lines 24 - 27, The warning
block uses informal phrasing "So, pretty please, do not force-push." — replace
that clause with a neutral, formal sentence (e.g., "Please avoid force-pushing."
or "Avoid force-pushing while a PR is open.") to match the document tone; update
the line within the warning block that ends with "So, pretty please, do not
force-push." and ensure punctuation and capitalization remain consistent with
the surrounding list items.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/developer/standardsandguidelines.md`:
- Around line 25-26: The admonition warning block contains list items using
asterisk markers ("*") which trigger markdownlint MD037; update the list markers
inside that admonition/warning block to use hyphens ("-") instead of asterisks
so the bullets become "- It has many subtle..." and "- For example, we regularly
lost review comments..." (i.e., replace each "*" list marker in the
warning/admonition block with "-" to remove MD037 noise).

---

Nitpick comments:
In `@docs/developer/standardsandguidelines.md`:
- Around line 24-27: The warning block uses informal phrasing "So, pretty
please, do not force-push." — replace that clause with a neutral, formal
sentence (e.g., "Please avoid force-pushing." or "Avoid force-pushing while a PR
is open.") to match the document tone; update the line within the warning block
that ends with "So, pretty please, do not force-push." and ensure punctuation
and capitalization remain consistent with the surrounding list items.
🪄 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: 5688a30d-8b1c-4ee7-9ff8-5caacf139a19

📥 Commits

Reviewing files that changed from the base of the PR and between e628e29 and a6b197d.

📒 Files selected for processing (2)
  • docs/developer/standardsandguidelines.md
  • src/HardwareSprite.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/HardwareSprite.h

Comment thread docs/developer/standardsandguidelines.md Outdated
gNbDmaBuffer → class member nbDmaBuffer:
  - Added uint8_t nbDmaBuffer = 6; to the class (replacing the commented-out placeholder)
  - Removed extern uint8_t gNbDmaBuffer; from .h and its definition from .cpp
  - All ~20 uses in class methods → nbDmaBuffer; ISR uses (via cont) → cont->nbDmaBuffer

  gNumStrips → removed, transpose16x1Noinline2 now takes numStrips parameter:
  - Added uint8_t numStrips parameter to the forward declaration and definition
  - Live call sites (5×) in loadAndTranspose pass driver->numStrips
  - Removed setGlobalNumStrips() method and both active call sites
  - Removed extern uint8_t gNumStrips; and its definition from .cpp
@ewowi

ewowi commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai, please review 54db938

@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== COMMIT 54db938 message ==="
gh api repos/hpwit/I2SClocklessLedDriver/commits/54db938cce6943f149791fb0558fecd12682959a --jq '.commit.message'

echo ""
echo "=== FILES CHANGED ==="
gh api repos/hpwit/I2SClocklessLedDriver/commits/54db938cce6943f149791fb0558fecd12682959a --jq '.files[] | "FILE: \(.filename) (+\(.additions)/-\(.deletions))"'

echo ""
echo "=== FULL DIFF ==="
gh api repos/hpwit/I2SClocklessLedDriver/commits/54db938cce6943f149791fb0558fecd12682959a --jq '.files[] | "=== FILE: \(.filename) ===\nPATCH:\n\(.patch // "(binary or no patch)")\n---"'

Length of output: 14812


@ewowi — Clean refactor. Here's the full breakdown:


gNbDmaBuffernbDmaBuffer (class member)

  • Default value 6 preserved via in-class initializer uint8_t nbDmaBuffer = 6;
  • All ~20 call sites in class methods updated consistently; ISR path uses cont->nbDmaBuffer correctly
  • nbDmaBuffer is not volatile, but that's correct: updateDriver() already waits for DMA to quiesce (500 ms semaphore) before writing nbDmaBuffer, so the ISR is never running at the same time the value is changed — no data race, no volatile needed
  • deleteDriver() loop bound nbDmaBuffer + 2 is unchanged in semantics

gNumStripsnumStrips parameter on transpose16x1Noinline2

  • Forward declaration and definition both updated to (unsigned char* a, uint16_t* b, uint8_t numStrips) — consistent
  • All 5 call sites in loadAndTranspose pass driver->numStrips — correct
  • setGlobalNumStrips() removed cleanly; both call sites in initLedImpl and updateDriver dropped. this->numStrips is already set by the time loadAndTranspose is called, so no value is lost
  • driver->numStrips is volatile uint8_t; passing it as a uint8_t parameter is a safe value-copy for an IRAM_ATTR function called from the ISR

✅ Net result

Both of the last two mutable globals in the driver are gone. This eliminates the only remaining multi-instance data-race hazard (two I2SClocklessLedDriver objects would have shared gNbDmaBuffer/gNumStrips), and makes the class fully self-contained.


docs/developer/standardsandguidelines.md

Trivial markdown cleanup — * bullets changed to -, one informal sentence removed. No functional change.


No issues found. This commit is a solid improvement with no regressions. ✅


🧠 Learnings used
Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T18:21:16.152Z
Learning: In the I2SClocklessLedDriver library (hpwit/I2SClocklessLedDriver), the user (ewowi) confirmed that the following are correctly implemented as of PR `#51` / commit 450d5c1: getStrip() bounds guard, getLengths() uint16_t* return type, createSubset clamping, frameBuffer null-deref protection with _offPixel sentinel and valid(), hardwareSprite NBSPRITE overflow guard, deleteDriver() nulling DMABuffersTampon, freeing target and _hmap, ISR semaphore logic, and main.cpp effect cycling. The namespace wrapper for struct Pixel / class Pixels / enum class leddirection in src/___pixeltypes.h is intentionally deferred as a missing feature (not a bug).

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51` (commit 77853ba), Pixels copy constructor creates a non-owning view (localLedPointer=false, mapFunction=nullptr, arguments=nullptr); assignment operator is deleted. This is intentional: copy semantics produce a view, not a deep copy. The namespace wrapper for Pixel/Pixels/leddirection is out of scope for this PR and deferred.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51`, tools/patch_compile_db.py uses shutil.copy2 to copy xtensa base headers directly into the ~/.platformio/packages/framework-arduinoespressif32-libs/ package tree (in-place), which corrupts the PlatformIO installation and causes "xthal_set_intset was not declared in this scope" errors in subsequent pio run builds. Fix: rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs to restore clean packages. Permanent fix: copy stubs into OUTPUT_DIR/xtensa_stubs/ instead and use -isystem pointing there.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 51
File: src/main.cpp:1-2
Timestamp: 2026-04-05T18:39:10.248Z
Learning: In hpwit/I2SClocklessLedDriver, `src/main.cpp` is the standalone demo entrypoint guarded by `#ifdef PLATFORM_VERSION`. It is safely excluded from library consumers by two independent mechanisms: (1) `library.json` `build.srcFilter` explicitly excludes `main.cpp` with `"-<main.cpp>"`, and (2) `PLATFORM_VERSION` is only defined within the PlatformIO dev environments in `platformio.ini` and is never set for library consumers. No rename or additional guard is needed.

@ewowi

ewowi commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai, I am about to merge this PR. Please produce three outputs:

  1. PR review — in-depth review of all commits: a concise summary of what changed and why, a merge recommendation, and a prioritised list of follow-up actions. For the most urgent items (blockers or high-risk changes), include a ready-to-paste prompt that a Claude Code agent can execute immediately before merge.

  2. End-user docs prompt — a ready-to-paste prompt for a Claude Code agent to update docs/enduser. Rules: only describe usage implications (what changed for the user); no internals, no code, no architecture; check existing pages before adding — update in place rather than duplicating; keep additions compact and user-friendly.

  3. Developer docs prompt — a ready-to-paste prompt for a Claude Code agent to update docs/developer. Rules: target contributors, not end users; be concise — if the detail is already in the code or commit messages, do not repeat it; focus on decisions, patterns, and guidance that are not obvious from reading the source.

@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me pull the full commit list and current file states to make the review comprehensive.


🏁 Script executed:

#!/bin/bash
echo "=== ALL COMMITS ON PR `#51` ==="
gh api "repos/hpwit/I2SClocklessLedDriver/pulls/51/commits" --jq '.[] | "\(.sha[0:8]) \(.commit.author.date[0:10]) \(.commit.message | split("\n")[0])"'

echo ""
echo "=== LATEST: src/I2SClocklessLedDriver.h (first 60 lines) ==="
head -60 src/I2SClocklessLedDriver.h

echo ""
echo "=== ets_delay_us occurrences ==="
rg -n 'ets_delay_us|esp_rom_delay_us' --type cpp -C1

echo ""
echo "=== compile_commands.json tracked? ==="
git ls-files compile_commands.json

echo ""
echo "=== .github/workflows/lint.yml (clang-tidy step) ==="
grep -n 'clang-tidy\|clang_exit\||| true\|output=' .github/workflows/lint.yml | head -30

echo ""
echo "=== tools/patch_compile_db.py (xtensa copy block) ==="
grep -n 'chip_xtensa\|shutil.copy2\|XTENSA_STUBS\|xtensa_stubs\|copy2' tools/patch_compile_db.py

echo ""
echo "=== src/I2SClocklessLedDriver.h: deprecated aliases? ==="
grep -n 'deprecated\|frameBuffer\|hardwareSprite\|leddirection\|colorarrangment\|displayMode' src/I2SClocklessLedDriver.h | head -20

echo ""
echo "=== CHANGELOG or breaking changes note ==="
ls CHANGELOG* CHANGES* 2>/dev/null || echo "(none)"

echo ""
echo "=== src/pixeltypes.h: Pixels copy ctor doc comment ==="
grep -n 'copy\|view\|shallow\|ownership\|assignment' src/pixeltypes.h | head -20

Length of output: 6285


@ewowi — Here are all three outputs.


1. PR Review

Summary of changes

Commit group What changed Why
676c57d382bbcf2c ESP32-S3 support; DMA buffer adaptation for IDF 5+; WiFi + effects demo; RGBW gamma bugfix; pins as uint8_t; IDF 5.5 compatibility checks; ESP32/D0 branching Bring dev up to date with hardware targets and IDF evolution
450d5c1 FrameBuffer null-deref protection (_offPixel, valid()); Pixels bounds guards; HardwareSprite NBSPRITE overflow guard; deleteDriver() full cleanup; ISR semaphore quiescing in updateDriver() Correctness hardening flagged in review
77853ba clang-tidy + cppcheck CI; CamelCase identifier renames; file renames; I2SClocklessLedDriver copy/move deleted Linting infrastructure + naming consistency
6196203 Self-assignment bugs fixed for brightness, gammar/g/b/w Compile-flag catch
54db938 gNbDmaBuffernbDmaBuffer class member; gNumStripsnumStrips parameter on transpose16x1Noinline2 Eliminated last two mutable globals; fixes multi-instance data-race hazard

Merge recommendation

Merge after fixing one blocker. Two ets_delay_us(16) calls survive in the ESP32 (non-S3) path at lines 1437 and 1574 of src/I2SClocklessLedDriver.h. ets_delay_us is no longer declared in IDF 5+ for the non-S3 path and will break the esp32dev build target. All other correctness issues have been resolved and confirmed.

Prioritised follow-up actions

🔴 Blocker — fix before merge

  1. ets_delay_us at lines 1437 and 1574 (src/I2SClocklessLedDriver.h, ESP32 non-S3 path) → replace with esp_rom_delay_us(16) and add #include "esp_rom_sys.h" if not yet present in that branch

🟠 High — fix immediately after merge (or same commit)
2. compile_commands.json still trackedgit rm --cached compile_commands.json && git commit -m "chore: untrack generated compile_commands.json" (.gitignore entry already present)
3. tools/patch_compile_db.py still mutates PlatformIO package tree — xtensa stubs are written into ~/.platformio/…/xtensa/ (see Change 3 diff provided earlier), causing xthal_set_intset build errors for subsequent pio run builds

🟡 Medium — first follow-up PR
4. Document breaking API renamesframeBufferFrameBuffer, leddirectionLedDirection, hardwareSpriteHardwareSprite, colorarrangmentColorArrangement, displayModeDisplayMode in a CHANGELOG.md or README section
5. Namespace wrapper (Pixel/Pixels/LedDirectionnamespace I2SClocklessLed) — intentionally deferred; remains the original PR objective for a follow-up


🤖 Claude Code agent prompt — Blocker fix (paste before merge)

In the file src/I2SClocklessLedDriver.h, there are two remaining bare `ets_delay_us(16)` calls
in the ESP32 (non-S3, non-P4) code path at approximately lines 1437 and 1574.
These cause a compile error on IDF 5+ because `ets_delay_us` is no longer in scope there.

Please do the following:

1. Search src/I2SClocklessLedDriver.h for every non-commented occurrence of `ets_delay_us`.
2. Replace each one with `esp_rom_delay_us`.
3. Check whether `#include "esp_rom_sys.h"` is already present in the non-S3 include block
   (the `#else` branch of `#ifdef CONFIG_IDF_TARGET_ESP32S3`).
   If it is missing, add it there.
4. Do NOT modify the commented-out line `// ets_delay_us(16);  // for sk6812` near line 1568 —
   that line is already superseded by the `esp_rom_delay_us` call on the next line.
5. After the changes, confirm zero remaining non-commented `ets_delay_us` calls in the file.
6. Do not change any other logic, formatting, or comments.

2. End-user docs prompt

You are updating docs/enduser/enduser.md for the I2SClocklessLedDriver library.
Read the existing file first; do not duplicate content — update in place.

The following changes affect library users and need to be reflected in the docs:

1. **Pin and length array types** — Example code should declare pin arrays as `uint8_t`
   (not `int`) and strip-length arrays as `uint16_t` (not `int`).
   Update any code snippets in the docs to match.

2. **Type renames (breaking)** — Add a compact "Migration / breaking changes" note
   (a small table or bullet list is fine):
   - `frameBuffer`  → `FrameBuffer`
   - `hardwareSprite` → `HardwareSprite`
   - `leddirection` → `LedDirection`   (also now an `enum class`; use `LedDirection::FORWARD` etc.)
   - `colorarrangment` → `ColorArrangement`
   - `displayMode` → `DisplayMode` (member variable and enum values unchanged)

3. **`Pixels` assignment is deleted** — `Pixels p = other;` (copy-init) works and creates
   a lightweight view. `p = other;` (assignment) is a compile error by design.
   Note this wherever the `Pixels` API is described.

4. **`updateDriver()` and `deleteDriver()`** — These are now public methods.
   `updateDriver()` reconfigures the driver at runtime (strips, pins, DMA depth) and
   safely quiesces any in-flight DMA before reconfiguring.
   `deleteDriver()` releases DMA resources and is safe to call multiple times.
   Add a short usage note under a "Runtime reconfiguration" heading if one does not exist;
   update it if it does.

5. **`FrameBuffer::valid()`** — The `FrameBuffer` class now exposes a `valid()` method
   that returns `false` if frame allocation failed. Mention this where FrameBuffer usage
   is described.

6. **`Pixels::getStrip()` safety** — Out-of-range strip indices now return an empty
   `Pixels` object (size 0, null pointer) instead of crashing. No user action needed;
   just note the safe behaviour if `getStrip()` is mentioned.

Rules: usage only — no internals, no ISR details, no architecture. Keep additions compact.

3. Developer docs prompt

You are updating docs/developer/ for the I2SClocklessLedDriver library.
Read all existing files in docs/developer/ before writing — update in place, do not duplicate.

Add or update the following developer-facing guidance:

1. **Why the last two mutable globals were removed (commit 54db938)**
   `gNbDmaBuffer` and `gNumStrips` were file-scope globals. With two
   `I2SClocklessLedDriver` instances they would share state, causing a data race.
   `gNbDmaBuffer` became `nbDmaBuffer` (class member, default 6, not `volatile`).
   It is safe without `volatile` because `updateDriver()` always waits for DMA to
   quiesce via semaphore before writing it; the ISR therefore never runs concurrently
   with a write.  `gNumStrips` became a parameter on `transpose16x1Noinline2()`; the
   ISR passes `driver->numStrips` (already `volatile`) directly.

2. **`Pixels` copy semantics — intentional asymmetry**
   The copy constructor produces a *non-owning view*: it copies `ledpointer` and sizes
   but sets `localLedPointer = false`, `mapFunction = nullptr`, `arguments = nullptr`.
   Assignment (`operator=`) is deleted to prevent silent shallow copies that could
   outlive the source buffer.  When documenting or reviewing `Pixels`, do not treat the
   missing assignment as a defect.

3. **`tools/patch_compile_db.py` — known issue with xtensa stubs**
   The current script copies xtensa base headers into the live PlatformIO package tree
   (`~/.platformio/packages/framework-arduinoespressif32-libs/…/xtensa/`).
   This corrupts subsequent `pio run` builds with
   `'xthal_set_intset' was not declared in this scope` errors.
   Recovery: `rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs`.
   Permanent fix (pending): write stubs to `OUTPUT_DIR/xtensa_stubs/` and inject via
   `-isystem` instead of mutating system packages.
   Note this as a known limitation until the fix is applied.

4. **`HardwareSprite::reorder()` — caller precondition**
   `target` carries no size metadata. The existing bounds check
   (`pixelOffset >= 0 && pixelOffset < width * height`) only validates within the
   declared dimensions. If the `target` allocation is actually smaller than
   `width * height`, writes will overflow silently.
   This is a deliberate trade-off: adding a `targetSize` parameter shifts the error
   surface without preventing it.  Document this as a caller precondition:
   *"Caller must ensure `target` points to a buffer of at least `width * height`
   `uint16_t` elements."*

5. **CI / lint workflow pattern**
   `clang-tidy` always exits non-zero on cross-compilation headers (xtensa/newlib).
   The workflow therefore uses `|| true` on the command substitution and only fails
   if the captured output contains violation lines matching `$(pwd)/src/`.
   Do not remove `|| true` or add `set -e` around the clang-tidy step.

Rules: target contributors only. Do not repeat what is already clear from reading the source.
Be concise — a short paragraph per item is enough.

🧠 Learnings used
Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T18:21:16.152Z
Learning: In the I2SClocklessLedDriver library (hpwit/I2SClocklessLedDriver), the user (ewowi) confirmed that the following are correctly implemented as of PR `#51` / commit 450d5c1: getStrip() bounds guard, getLengths() uint16_t* return type, createSubset clamping, frameBuffer null-deref protection with _offPixel sentinel and valid(), hardwareSprite NBSPRITE overflow guard, deleteDriver() nulling DMABuffersTampon, freeing target and _hmap, ISR semaphore logic, and main.cpp effect cycling. The namespace wrapper for struct Pixel / class Pixels / enum class leddirection in src/___pixeltypes.h is intentionally deferred as a missing feature (not a bug).

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51` (commit 77853ba), Pixels copy constructor creates a non-owning view (localLedPointer=false, mapFunction=nullptr, arguments=nullptr); assignment operator is deleted. This is intentional: copy semantics produce a view, not a deep copy. The namespace wrapper for Pixel/Pixels/leddirection is out of scope for this PR and deferred.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 0
File: :0-0
Timestamp: 2026-04-05T21:23:32.034Z
Learning: In hpwit/I2SClocklessLedDriver PR `#51`, tools/patch_compile_db.py uses shutil.copy2 to copy xtensa base headers directly into the ~/.platformio/packages/framework-arduinoespressif32-libs/ package tree (in-place), which corrupts the PlatformIO installation and causes "xthal_set_intset was not declared in this scope" errors in subsequent pio run builds. Fix: rm -rf ~/.platformio/packages/framework-arduinoespressif32-libs to restore clean packages. Permanent fix: copy stubs into OUTPUT_DIR/xtensa_stubs/ instead and use -isystem pointing there.

Learnt from: ewowi
Repo: hpwit/I2SClocklessLedDriver PR: 51
File: src/main.cpp:1-2
Timestamp: 2026-04-05T18:39:10.248Z
Learning: In hpwit/I2SClocklessLedDriver, `src/main.cpp` is the standalone demo entrypoint guarded by `#ifdef PLATFORM_VERSION`. It is safely excluded from library consumers by two independent mechanisms: (1) `library.json` `build.srcFilter` explicitly excludes `main.cpp` with `"-<main.cpp>"`, and (2) `PLATFORM_VERSION` is only defined within the PlatformIO dev environments in `platformio.ini` and is never set for library consumers. No rename or additional guard is needed.

ewowi and others added 2 commits April 6, 2026 12:30
File is already in .gitignore but was committed before the entry was added.
Stale absolute paths from the original machine prevented lint regeneration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/I2SClocklessLedDriver.cpp`:
- Around line 39-52: Move the semaphore creation so waitDisp is allocated before
setting wasWaitingtofinish and adjust the control flow to always clear
wasWaitingtofinish on all exit paths; specifically, call
xSemaphoreCreateCounting (assign to waitDisp) prior to setting
wasWaitingtofinish=true, check for NULL and log/return if allocation fails, then
perform xSemaphoreTake, and ensure wasWaitingtofinish is reset to false both on
timeout (before returning) and after successful take so that any ISR calling
xSemaphoreGiveFromISR(cont->waitDisp, ...) never runs with a NULL handle and the
semaphore count is not corrupted.
🪄 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: 335474a1-3547-4823-a889-d190e78922c0

📥 Commits

Reviewing files that changed from the base of the PR and between a6b197d and 30ac019.

📒 Files selected for processing (4)
  • .coderabbit.yaml
  • docs/developer/standardsandguidelines.md
  • src/I2SClocklessLedDriver.cpp
  • src/I2SClocklessLedDriver.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • .coderabbit.yaml

Comment thread src/I2SClocklessLedDriver.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
docs/developer/developer.md (1)

9-9: ⚠️ Potential issue | 🟡 Minor

Update the file reference to match the actual header.

The table entry still points to src/pixeltypes.h, but the actual header in the codebase is src/___pixeltypes.h.

📝 Suggested fix
-| `src/pixeltypes.h` | `Pixel` struct and `Pixels` container (used when `USE_PIXELSLIB` is not set) |
+| `src/___pixeltypes.h` | `Pixel` struct and `Pixels` container (used when `USE_PIXELSLIB` is not set) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/developer/developer.md` at line 9, Update the documentation table entry
that currently references src/pixeltypes.h to use the actual header name
src/___pixeltypes.h; locate the line in docs/developer/developer.md containing
the table row with "`src/pixeltypes.h` | `Pixel` struct and `Pixels` container"
and replace the first code cell with "`src/___pixeltypes.h`" so the doc matches
the real header used when USE_PIXELSLIB is not set.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/developer/developer.md`:
- Line 9: Update the documentation table entry that currently references
src/pixeltypes.h to use the actual header name src/___pixeltypes.h; locate the
line in docs/developer/developer.md containing the table row with
"`src/pixeltypes.h` | `Pixel` struct and `Pixels` container" and replace the
first code cell with "`src/___pixeltypes.h`" so the doc matches the real header
used when USE_PIXELSLIB is not set.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cbce8608-e709-4a2e-b22d-ba2241f2d369

📥 Commits

Reviewing files that changed from the base of the PR and between 30ac019 and 85a5202.

📒 Files selected for processing (2)
  • docs/developer/developer.md
  • docs/enduser/enduser.md
✅ Files skipped from review due to trivial changes (1)
  • docs/enduser/enduser.md

@ewowi
ewowi merged commit a736f5e into main Apr 6, 2026
14 checks passed
@ewowi
ewowi deleted the dev branch April 6, 2026 11:16
@coderabbitai coderabbitai Bot mentioned this pull request Apr 6, 2026
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.

4 participants