mirror of
https://github.com/wled/WLED.git
synced 2025-11-09 19:18:54 +00:00
Compare commits
2 Commits
coderabbit
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25a875e4d9 | ||
|
|
ed3b092a47 |
53
.github/copilot-instructions.md
vendored
53
.github/copilot-instructions.md
vendored
@@ -30,27 +30,6 @@ The build has two main phases:
|
|||||||
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
|
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
|
||||||
- List all targets: `pio run --list-targets`
|
- List all targets: `pio run --list-targets`
|
||||||
|
|
||||||
## Before Finishing Work
|
|
||||||
|
|
||||||
**CRITICAL: You MUST complete ALL of these steps before marking your work as complete:**
|
|
||||||
|
|
||||||
1. **Run the test suite**: `npm test` -- Set timeout to 2+ minutes. NEVER CANCEL.
|
|
||||||
- All tests MUST pass
|
|
||||||
- If tests fail, fix the issue before proceeding
|
|
||||||
|
|
||||||
2. **Build at least one hardware environment**: `pio run -e esp32dev` -- Set timeout to 30+ minutes. NEVER CANCEL.
|
|
||||||
- Choose `esp32dev` as it's a common, representative environment
|
|
||||||
- See "Hardware Compilation" section above for the full list of common environments
|
|
||||||
- The build MUST complete successfully without errors
|
|
||||||
- If the build fails, fix the issue before proceeding
|
|
||||||
- **DO NOT skip this step** - it validates that firmware compiles with your changes
|
|
||||||
|
|
||||||
3. **For web UI changes only**: Manually test the interface
|
|
||||||
- See "Manual Testing Scenarios" section below
|
|
||||||
- Verify the UI loads and functions correctly
|
|
||||||
|
|
||||||
**If any of these validation steps fail, you MUST fix the issues before finishing. Do NOT mark work as complete with failing builds or tests.**
|
|
||||||
|
|
||||||
## Validation and Testing
|
## Validation and Testing
|
||||||
|
|
||||||
### Web UI Testing
|
### Web UI Testing
|
||||||
@@ -65,7 +44,7 @@ The build has two main phases:
|
|||||||
- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
|
- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
|
||||||
- **C++ formatting available**: `clang-format` is installed but not in CI
|
- **C++ formatting available**: `clang-format` is installed but not in CI
|
||||||
- **Always run tests before finishing**: `npm test`
|
- **Always run tests before finishing**: `npm test`
|
||||||
- **MANDATORY: Always run a hardware build before finishing** (see "Before Finishing Work" section below)
|
- **Always run a build for the common environment before finishing**
|
||||||
|
|
||||||
### Manual Testing Scenarios
|
### Manual Testing Scenarios
|
||||||
After making changes to web UI, always test:
|
After making changes to web UI, always test:
|
||||||
@@ -121,16 +100,10 @@ package.json # Node.js dependencies and scripts
|
|||||||
|
|
||||||
## Build Timing and Timeouts
|
## Build Timing and Timeouts
|
||||||
|
|
||||||
**IMPORTANT: Use these timeout values when running builds:**
|
- **Web UI build**: 3 seconds - Set timeout to 30 seconds minimum
|
||||||
|
- **Test suite**: 40 seconds - Set timeout to 2 minutes minimum
|
||||||
- **Web UI build** (`npm run build`): 3 seconds typical - Set timeout to 30 seconds minimum
|
- **Hardware builds**: 15+ minutes - Set timeout to 30+ minutes minimum
|
||||||
- **Test suite** (`npm test`): 40 seconds typical - Set timeout to 120 seconds (2 minutes) minimum
|
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation can take significant time
|
||||||
- **Hardware builds** (`pio run -e [target]`): 15-20 minutes typical for first build - Set timeout to 1800 seconds (30 minutes) minimum
|
|
||||||
- Subsequent builds are faster due to caching
|
|
||||||
- First builds download toolchains and dependencies which takes significant time
|
|
||||||
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation require patience
|
|
||||||
|
|
||||||
**When validating your changes before finishing, you MUST wait for the hardware build to complete successfully. Set the timeout appropriately and be patient.**
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -156,17 +129,11 @@ package.json # Node.js dependencies and scripts
|
|||||||
- **Hardware builds require appropriate ESP32/ESP8266 development board**
|
- **Hardware builds require appropriate ESP32/ESP8266 development board**
|
||||||
|
|
||||||
## CI/CD Pipeline
|
## CI/CD Pipeline
|
||||||
|
The GitHub Actions workflow:
|
||||||
**The GitHub Actions CI workflow will:**
|
|
||||||
1. Installs Node.js and Python dependencies
|
1. Installs Node.js and Python dependencies
|
||||||
2. Runs `npm test` to validate build system (MUST pass)
|
2. Runs `npm test` to validate build system
|
||||||
3. Builds web UI with `npm run build` (automatically run by PlatformIO)
|
3. Builds web UI with `npm run build`
|
||||||
4. Compiles firmware for ALL hardware targets listed in `default_envs` (MUST succeed for all)
|
4. Compiles firmware for multiple hardware targets
|
||||||
5. Uploads build artifacts
|
5. Uploads build artifacts
|
||||||
|
|
||||||
**To ensure CI success, you MUST locally:**
|
Match this workflow in your local development to ensure CI success.
|
||||||
- Run `npm test` and ensure it passes
|
|
||||||
- Run `pio run -e esp32dev` (or another common environment from "Hardware Compilation" section) and ensure it completes successfully
|
|
||||||
- If either fails locally, it WILL fail in CI
|
|
||||||
|
|
||||||
**Match this workflow in your local development to ensure CI success. Do not mark work complete until you have validated builds locally.**
|
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ board_build.partitions = ${esp32.large_partitions}
|
|||||||
board_upload.flash_size = 8MB
|
board_upload.flash_size = 8MB
|
||||||
board_upload.maximum_size = 8388608
|
board_upload.maximum_size = 8388608
|
||||||
; board_build.f_flash = 80000000L
|
; board_build.f_flash = 80000000L
|
||||||
board_build.flash_mode = dio
|
; board_build.flash_mode = qio
|
||||||
|
|
||||||
[env:esp32dev_16M]
|
[env:esp32dev_16M]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
|
|||||||
@@ -135,8 +135,7 @@ uint16_t mode_copy_segment(void) {
|
|||||||
SEGMENT.fadeToBlackBy(5); // fade out
|
SEGMENT.fadeToBlackBy(5); // fade out
|
||||||
return FRAMETIME;
|
return FRAMETIME;
|
||||||
}
|
}
|
||||||
Segment& sourcesegment = strip.getSegment(sourceid);
|
Segment sourcesegment = strip.getSegment(sourceid);
|
||||||
|
|
||||||
if (sourcesegment.isActive()) {
|
if (sourcesegment.isActive()) {
|
||||||
uint32_t sourcecolor;
|
uint32_t sourcecolor;
|
||||||
uint32_t destcolor;
|
uint32_t destcolor;
|
||||||
@@ -678,7 +677,7 @@ uint16_t mode_twinkle(void) {
|
|||||||
SEGENV.step = it;
|
SEGENV.step = it;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t PRNG16 = SEGENV.aux1;
|
unsigned PRNG16 = SEGENV.aux1;
|
||||||
|
|
||||||
for (unsigned i = 0; i < SEGENV.aux0; i++)
|
for (unsigned i = 0; i < SEGENV.aux0; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -625,9 +625,6 @@ class Segment {
|
|||||||
DEBUGFX_PRINTLN();
|
DEBUGFX_PRINTLN();
|
||||||
#endif
|
#endif
|
||||||
clearName();
|
clearName();
|
||||||
#ifdef WLED_ENABLE_GIF
|
|
||||||
endImagePlayback(this);
|
|
||||||
#endif
|
|
||||||
deallocateData();
|
deallocateData();
|
||||||
p_free(pixels);
|
p_free(pixels);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ void WS2812FX::setUpMatrix() {
|
|||||||
|
|
||||||
// delete gap array as we no longer need it
|
// delete gap array as we no longer need it
|
||||||
p_free(gapTable);
|
p_free(gapTable);
|
||||||
resume();
|
// NOTE: do not resume() here; caller must call resume() after segments are updated
|
||||||
|
|
||||||
#ifdef WLED_DEBUG
|
#ifdef WLED_DEBUG
|
||||||
DEBUG_PRINT(F("Matrix ledmap:"));
|
DEBUG_PRINT(F("Matrix ledmap:"));
|
||||||
@@ -130,6 +130,7 @@ void WS2812FX::setUpMatrix() {
|
|||||||
Segment::maxWidth = _length;
|
Segment::maxWidth = _length;
|
||||||
Segment::maxHeight = 1;
|
Segment::maxHeight = 1;
|
||||||
resetSegments();
|
resetSegments();
|
||||||
|
resume(); // resume here since resetSegments() was called
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -448,9 +448,6 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
|
|||||||
|
|
||||||
// apply change immediately
|
// apply change immediately
|
||||||
if (i2 <= i1) { //disable segment
|
if (i2 <= i1) { //disable segment
|
||||||
#ifdef WLED_ENABLE_GIF
|
|
||||||
endImagePlayback(this);
|
|
||||||
#endif
|
|
||||||
deallocateData();
|
deallocateData();
|
||||||
p_free(pixels);
|
p_free(pixels);
|
||||||
pixels = nullptr;
|
pixels = nullptr;
|
||||||
@@ -469,9 +466,6 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
|
|||||||
#endif
|
#endif
|
||||||
// safety check
|
// safety check
|
||||||
if (start >= stop || startY >= stopY) {
|
if (start >= stop || startY >= stopY) {
|
||||||
#ifdef WLED_ENABLE_GIF
|
|
||||||
endImagePlayback(this);
|
|
||||||
#endif
|
|
||||||
deallocateData();
|
deallocateData();
|
||||||
p_free(pixels);
|
p_free(pixels);
|
||||||
pixels = nullptr;
|
pixels = nullptr;
|
||||||
@@ -485,9 +479,6 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
|
|||||||
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
|
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
|
||||||
if (!pixels) {
|
if (!pixels) {
|
||||||
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
|
||||||
#ifdef WLED_ENABLE_GIF
|
|
||||||
endImagePlayback(this);
|
|
||||||
#endif
|
|
||||||
deallocateData();
|
deallocateData();
|
||||||
errorFlag = ERR_NORAM_PX;
|
errorFlag = ERR_NORAM_PX;
|
||||||
stop = 0;
|
stop = 0;
|
||||||
|
|||||||
@@ -72,10 +72,11 @@ uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) {
|
|||||||
// video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue
|
// video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue
|
||||||
uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels
|
uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels
|
||||||
uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // determine dominant channel for hue preservation
|
uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // determine dominant channel for hue preservation
|
||||||
addRemains = r && (r<<5) > maxc ? 0x00010000 : 0; // note: setting color preservation threshold too high results in flickering and
|
uint8_t quarterMax = maxc >> 2; // note: using half of max results in color artefacts
|
||||||
addRemains |= g && (g<<5) > maxc ? 0x00000100 : 0; // jumping colors in low brightness gradients. Multiplying the color preserves
|
addRemains = r && r > quarterMax ? 0x00010000 : 0;
|
||||||
addRemains |= b && (b<<5) > maxc ? 0x00000001 : 0; // better accuracy than dividing the maxc. Shifting by 5 is a good compromise
|
addRemains |= g && g > quarterMax ? 0x00000100 : 0;
|
||||||
addRemains |= w ? 0x01000000 : 0; // i.e. remove color channel if <13% of max
|
addRemains |= b && b > quarterMax ? 0x00000001 : 0;
|
||||||
|
addRemains |= w ? 0x01000000 : 0;
|
||||||
}
|
}
|
||||||
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||||
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
|
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
|
||||||
|
|||||||
@@ -64,22 +64,6 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deserialize a segment description from a JSON object and apply it to the specified segment slot.
|
|
||||||
*
|
|
||||||
* Parses and applies geometry, naming, grouping/spacing/offset, 2D bounds, mode, palette, colors
|
|
||||||
* (supports kelvin, hex, "r" random, object or array formats), per-LED assignments, options (on/frz/sel/rev/mi),
|
|
||||||
* speed/intensity, custom channels, checks, blend mode, and LOXONE mappings. The function may append a new
|
|
||||||
* segment, delete a segment, perform a repeat expansion to create multiple segments, and mark global state
|
|
||||||
* as changed when segment parameters differ.
|
|
||||||
*
|
|
||||||
* @param elem JSON object describing the segment (API format).
|
|
||||||
* @param it Default segment index to use when `elem["id"]` is not provided.
|
|
||||||
* @param presetId Optional preset identifier; when nonzero, preset-related side effects (e.g., playlist unloading)
|
|
||||||
* are suppressed or handled differently.
|
|
||||||
* @return true if the JSON was valid for the target id and the segment was applied (or created); false if the
|
|
||||||
* target id is out of range or the descriptor is invalid (e.g., attempting to create an empty segment).
|
|
||||||
*/
|
|
||||||
static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0)
|
static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0)
|
||||||
{
|
{
|
||||||
byte id = elem["id"] | it;
|
byte id = elem["id"] | it;
|
||||||
@@ -221,7 +205,7 @@ static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0)
|
|||||||
// JSON "col" array can contain the following values for each of segment's colors (primary, background, custom):
|
// JSON "col" array can contain the following values for each of segment's colors (primary, background, custom):
|
||||||
// "col":[int|string|object|array, int|string|object|array, int|string|object|array]
|
// "col":[int|string|object|array, int|string|object|array, int|string|object|array]
|
||||||
// int = Kelvin temperature or 0 for black
|
// int = Kelvin temperature or 0 for black
|
||||||
// string = hex representation of [WW]RRGGBB or "r" for random color
|
// string = hex representation of [WW]RRGGBB
|
||||||
// object = individual channel control {"r":0,"g":127,"b":255,"w":255}, each being optional (valid to send {})
|
// object = individual channel control {"r":0,"g":127,"b":255,"w":255}, each being optional (valid to send {})
|
||||||
// array = direct channel values [r,g,b,w] (w element being optional)
|
// array = direct channel values [r,g,b,w] (w element being optional)
|
||||||
int rgbw[] = {0,0,0,0};
|
int rgbw[] = {0,0,0,0};
|
||||||
@@ -245,9 +229,6 @@ static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0)
|
|||||||
if (kelvin == 0) seg.setColor(i, 0);
|
if (kelvin == 0) seg.setColor(i, 0);
|
||||||
if (kelvin > 0) colorKtoRGB(kelvin, brgbw);
|
if (kelvin > 0) colorKtoRGB(kelvin, brgbw);
|
||||||
colValid = true;
|
colValid = true;
|
||||||
} else if (hexCol[0] == 'r' && hexCol[1] == '\0') { // Random colors via JSON API in Segment object like col=["r","r","r"] · Issue #4996
|
|
||||||
setRandomColor(brgbw);
|
|
||||||
colValid = true;
|
|
||||||
} else { //HEX string, e.g. "FFAA00"
|
} else { //HEX string, e.g. "FFAA00"
|
||||||
colValid = colorFromHexString(brgbw, hexCol);
|
colValid = colorFromHexString(brgbw, hexCol);
|
||||||
}
|
}
|
||||||
@@ -1259,4 +1240,4 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient)
|
|||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -817,6 +817,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
strip.panel.shrink_to_fit(); // release unused memory
|
strip.panel.shrink_to_fit(); // release unused memory
|
||||||
strip.deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
|
strip.deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
|
||||||
strip.makeAutoSegments(true); // force re-creation of segments
|
strip.makeAutoSegments(true); // force re-creation of segments
|
||||||
|
strip.resume(); // resume strip service after 2D config changes are complete
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -896,8 +896,7 @@ static bool detectBootLoop() {
|
|||||||
bl_crashcounter++;
|
bl_crashcounter++;
|
||||||
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
|
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
|
||||||
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
|
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
|
||||||
bl_crashcounter = 0;
|
bl_crashcounter = 0;
|
||||||
if(bl_actiontracker > BOOTLOOP_ACTION_DUMP) bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // reset action tracker if out of bounds
|
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ void WLED::loop()
|
|||||||
strip.finalizeInit(); // will create buses and also load default ledmap if present
|
strip.finalizeInit(); // will create buses and also load default ledmap if present
|
||||||
if (aligned) strip.makeAutoSegments();
|
if (aligned) strip.makeAutoSegments();
|
||||||
else strip.fixInvalidSegments();
|
else strip.fixInvalidSegments();
|
||||||
|
strip.resume(); // resume strip service after bus re-initialization
|
||||||
BusManager::setBrightness(scaledBri(bri)); // fix re-initialised bus' brightness #4005 and #4824
|
BusManager::setBrightness(scaledBri(bri)); // fix re-initialised bus' brightness #4005 and #4824
|
||||||
configNeedsWrite = true;
|
configNeedsWrite = true;
|
||||||
}
|
}
|
||||||
@@ -563,6 +564,7 @@ void WLED::beginStrip()
|
|||||||
strip.setTransition(0); // temporarily prevent transitions to reduce segment copies
|
strip.setTransition(0); // temporarily prevent transitions to reduce segment copies
|
||||||
strip.finalizeInit(); // busses created during deserializeConfig() if config existed
|
strip.finalizeInit(); // busses created during deserializeConfig() if config existed
|
||||||
strip.makeAutoSegments();
|
strip.makeAutoSegments();
|
||||||
|
strip.resume(); // resume strip service after initialization
|
||||||
strip.setBrightness(0);
|
strip.setBrightness(0);
|
||||||
strip.setShowCallback(handleOverlayDraw);
|
strip.setShowCallback(handleOverlayDraw);
|
||||||
doInitBusses = false;
|
doInitBusses = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user