Compare commits

...

1 Commits

Author SHA1 Message Date
coderabbitai[bot]
6ca83b7647 📝 Add docstrings to more_customPalettes
Docstrings generation was requested by @DedeHai.

* https://github.com/wled/WLED/pull/4932#issuecomment-3289882539

The following files were modified:

* `tools/cdata.js`
* `wled00/FX_fcn.cpp`
* `wled00/colors.cpp`
* `wled00/colors.h`
* `wled00/data/index.js`
* `wled00/json.cpp`
* `wled00/util.cpp`
* `wled00/wled_server.cpp`
2025-09-14 20:46:52 +00:00
8 changed files with 237 additions and 41 deletions

View File

@@ -126,6 +126,20 @@ async function minify(str, type = "plain") {
throw new Error("Unknown filter: " + type);
}
/**
* Inline-depends, minifies, gzip-compresses an HTML source and writes a C header array.
*
* Reads the HTML at sourceFile, inlines referenced resources, replaces repo/version placeholders,
* minifies the HTML, compresses it with gzip, converts the compressed bytes to a C-style hex array,
* and writes a header file to resultFile that defines:
* - const uint16_t PAGE_<page>_length = <length>;
* - const uint8_t PAGE_<page>[] PROGMEM = { ... };
*
* @param {string} sourceFile - Path to the source HTML file to inline and compress.
* @param {string} resultFile - Path where the generated C header file will be written.
* @param {string} page - Identifier used to name the generated symbols (e.g., "index", "pixart").
* @throws {Error} If inlining the HTML fails (propagates the inline error).
*/
async function writeHtmlGzipped(sourceFile, resultFile, page) {
console.info("Reading " + sourceFile);
inline.html({
@@ -143,7 +157,7 @@ async function writeHtmlGzipped(sourceFile, resultFile, page) {
console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes");
const array = hexdump(result);
let src = singleHeader;
src += `const uint16_t PAGE_${page}_L = ${result.length};\n`;
src += `const uint16_t PAGE_${page}_length = ${result.length};\n`;
src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`;
console.info("Writing " + resultFile);
fs.writeFileSync(resultFile, src);
@@ -244,9 +258,22 @@ if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.ar
writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart');
writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
//writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic');
writeChunks(
"wled00/data/cpal",
[
{
file: "cpal.htm",
name: "PAGE_cpal",
method: "gzip",
filter: "html-minify"
}
],
"wled00/html_cpal.h"
);
writeChunks(
"wled00/data",
[

View File

@@ -11,7 +11,6 @@
*/
#include "wled.h"
#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
#include "palettes.h"
/*
Custom per-LED mapping has moved!
@@ -200,12 +199,15 @@ void Segment::deallocateData() {
}
/**
* If reset of this segment was requested, clears runtime
* settings of this segment.
* Must not be called while an effect mode function is running
* because it could access the data buffer and this method
* may free that data buffer.
*/
* @brief Clear pending runtime state when a segment is marked for reset.
*
* If this segment's reset flag is set and the segment is active, zeroes the
* segment's runtime data buffer (to avoid heap fragmentation), clears the
* pixel buffer, resets timing/step/call/aux counters, clears the reset flag,
* and, when GIF playback support is enabled, ends any ongoing image playback.
*
* Note: this routine is safe to call only when no effect mode is currently
* running for the segment — effect code may access the data/pixel buffers. */
void Segment::resetIfRequired() {
if (!reset || !isActive()) return;
//DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
@@ -218,9 +220,31 @@ void Segment::resetIfRequired() {
#endif
}
/**
* @brief Selects and loads a palette into the supplied CRGBPalette16.
*
* Loads a palette identified by the numeric index `pal` into `targetPalette`.
* Supported palette sources (by index):
* - 0: the effect/default palette (resolved from _default_palette)
* - 1: runtime-random palette
* - 25: palettes derived from this segment's color slots (primary/secondary/tertiary combinations)
* - fastLED and built-in gradient palettes: mapped from the next contiguous index range
* - custom palettes: addressed from the high end (255, 254, ...) and stored in customPalettes
*
* If `pal` is outside the valid range for built-in/gradient/fastled indices it will be treated as 0
* (the default palette). When a custom palette index is selected it is loaded from customPalettes.
*
* @param targetPalette Palette object to populate (returned by reference).
* @param pal Numeric palette index selecting the source and layout (see summary above).
* @return CRGBPalette16& Reference to the populated `targetPalette`.
*/
CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0;
if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0;
// there is one randomy generated palette (1) followed by 4 palettes created from segment colors (2-5)
// those are followed by 7 fastled palettes (6-12) and 59 gradient palettes (13-71)
// then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
// palette 0 is a varying palette depending on effect and may be replaced by segment's color if so
// instructed in color_from_palette()
if (pal > FIXED_PALETTE_COUNT && pal < 255-customPalettes.size()+1) pal = 0; // out of bounds palette
//default palette. Differs depending on effect
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
switch (pal) {
@@ -256,13 +280,13 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
}
break;}
default: //progmem palettes
if (pal>245) {
if (pal > 255 - customPalettes.size()) {
targetPalette = customPalettes[255-pal]; // we checked bounds above
} else if (pal < 13) { // palette 6 - 12, fastled palettes
targetPalette = *fastledPalettes[pal-6];
} else if (pal < DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT+1) { // palette 6 - 12, fastled palettes
targetPalette = *fastledPalettes[pal-DYNAMIC_PALETTE_COUNT-1];
} else {
byte tcp[72];
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72);
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-(DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT)-1])), 72);
targetPalette.loadDynamicGradientPalette(tcp);
}
break;
@@ -529,6 +553,24 @@ Segment &Segment::setOption(uint8_t n, bool val) {
return *this;
}
/**
* @brief Set the effect (mode) for this segment.
*
* Sets the segment's mode to the first non-reserved effect at or after the
* provided index, optionally loading the effect's default parameters. If the
* new mode differs from the current one, a transition is started (a segment
* copy is created), the mode-specific defaults and palette are applied when
* requested, and the segment is marked for reset and state broadcast.
*
* @param fx Index of the desired effect/mode. If this index points to a
* reserved mode the next non-reserved mode is used. If the index is
* out of range the solid mode (index 0) is selected.
* @param loadDefaults When true, extract and apply the effect's default
* parameters (speed, intensity, custom values, mapping
* flags, sound simulation, mirror/reverse flags, etc.)
* and set the palette default when present.
* @return Segment& Reference to this segment (allows chaining).
*/
Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
// skip reserved
while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++;
@@ -565,9 +607,21 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
return *this;
}
/**
* @brief Set the segment's palette by index.
*
* Validates the supplied palette index and, if it differs from the current
* palette, begins a palette transition and marks the segment's state as
* changed so the new palette is propagated to clients/hardware.
*
* If the provided index is outside the range of built-in or custom palettes,
* it is normalized to 0.
*
* @param pal Palette index (may be adjusted to a valid value).
* @return Segment& Reference to this segment (for chaining).
*/
Segment &Segment::setPalette(uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes
if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; // custom palettes
if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette
if (pal != palette) {
//DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment)

View File

@@ -248,11 +248,29 @@ CRGBPalette16 generateRandomPalette() // generate fully random palette
CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)));
}
/**
* @brief Loads user-defined color palettes from filesystem into runtime storage.
*
* Scans for files named "/palette0.json", "/palette1.json", ... up to
* WLED_MAX_CUSTOM_PALETTES and builds dynamic gradient palettes from any valid
* JSON found. Existing in-memory custom palettes are cleared before loading.
*
* Supported JSON formats for the "palette" array:
* - Pairs of [index, hexString] (e.g. [0, "FF0000", 128, "00FF00", ...]) where
* each pair is an index (0255) followed by an RRGGBB or RRGGBBWW hex color.
* - Quads of [index, R, G, B] (e.g. [0, 255, 0, 0, 128, 0, 255, 0, ...]) where
* each group of four values is an index (0255) followed by red/green/blue bytes.
*
* For each palette file the function converts the supplied entries into a
* temporary gradient table (supporting up to 18 color stops) and appends the
* resulting CRGBPalette16 to customPalettes. The loader stops at the first
* missing palette file.
*/
void loadCustomPalettes() {
byte tcp[72]; //support gradient palettes with up to 18 entries
CRGBPalette16 targetPalette;
customPalettes.clear(); // start fresh
for (int index = 0; index<10; index++) {
for (int index = 0; index < WLED_MAX_CUSTOM_PALETTES; index++) {
char fileName[32];
sprintf_P(fileName, PSTR("/palette%d.json"), index);

View File

@@ -123,7 +123,21 @@ CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette();
void loadCustomPalettes();
extern std::vector<CRGBPalette16> customPalettes;
inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); }
/**
* Get the total number of available palettes (built-in fixed palettes plus user-defined custom palettes).
*
* @return Total palette count (FIXED_PALETTE_COUNT + customPalettes.size()).
*/
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); }
/**
* Pack an RGBW byte array into a 32-bit color value.
*
* The input must point to at least four bytes in order: R, G, B, W.
* Returns a uint32_t with layout 0xWWRRGGBB (white in the highest byte).
*
* @param rgbw Pointer to 4 bytes: {R, G, B, W}.
* @return 32-bit packed color in 0xWWRRGGBB format.
*/
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
@@ -141,4 +155,8 @@ void setRandomColor(byte* rgb);
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false);
// palettes
extern const TProgmemRGBPalette16* const fastledPalettes[];
extern const uint8_t* const gGradientPalettes[];
#endif

View File

@@ -1025,6 +1025,23 @@ function redrawPalPrev()
});
}
/**
* Generate a CSS background rule showing a horizontal gradient preview for a palette.
*
* Uses the global `palettesData` array for palette entries. Each palette entry may be:
* - an array [posByte, r, g, b] where `posByte` is 0..255 mapped to 0..100%,
* - the literal 'r' to insert a random RGB color, or
* - a reference value whose second character is treated as a 1-based index into the DOM color-slot list (element with id "csl") and reads its `data-r`, `data-g`, `data-b` attributes.
*
* Special cases:
* - If the palette contains a single color, the function duplicates it to produce a two-color gradient.
* - If `palettesData` is not defined the function returns undefined.
* - If the requested palette id is not found the function returns the string `'display: none'`.
*
* @param {number|string} id - Palette identifier (index or key) into `palettesData`.
* @return {string|undefined} CSS declaration for a left-to-right linear-gradient (e.g. `"background: linear-gradient(to right, ...);"`),
* `'display: none'` when the palette is missing, or `undefined` if `palettesData` is not available.
*/
function genPalPrevCss(id)
{
if (!palettesData) return;
@@ -1042,8 +1059,7 @@ function genPalPrevCss(id)
}
var gradient = [];
for (let j = 0; j < paletteData.length; j++) {
const e = paletteData[j];
paletteData.forEach((e,j) => {
let r, g, b;
let index = false;
if (Array.isArray(e)) {
@@ -1065,9 +1081,8 @@ function genPalPrevCss(id)
if (index === false) {
index = Math.round(j / paletteData.length * 100);
}
gradient.push(`rgb(${r},${g},${b}) ${index}%`);
}
});
return `background: linear-gradient(to right,${gradient.join()});`;
}
@@ -3086,15 +3101,29 @@ let iSlide = 0, x0 = null, scrollS = 0, locked = false;
function unify(e) { return e.changedTouches ? e.changedTouches[0] : e; }
/**
* Return true if any class name in the provided list starts with "Iro".
*
* @param {Iterable<string>} classList - An iterable of class name strings (e.g., Element.classList or an array).
* @returns {boolean} True when at least one class name begins with "Iro", otherwise false.
*/
function hasIroClass(classList)
{
for (var i = 0; i < classList.length; i++) {
var element = classList[i];
if (element.startsWith('Iro')) return true;
}
return false;
let found = false;
classList.forEach((e)=>{ if (e.startsWith('Iro')) found = true; });
return found;
}
//required by rangetouch.js
/**
* Handle touch/drag start to lock page scrolling and initiate horizontal slide gestures.
*
* If the app is in PC mode or simplified UI, or the event target (or its parent) is marked
* to skip sliding (has class `noslide` or contains iro-related classes), the function returns
* without side effects. Otherwise it records the initial pointer X position and current
* scrollTop into globals used by the gesture handler, sets the global `locked` flag, and
* toggles the container's `smooth` class accordingly.
*
* @param {Event} e - Pointer/touch event from rangetouch (the originating target is inspected).
*/
function lock(e)
{
if (pcMode || simplifiedUI) return;

View File

@@ -1,7 +1,5 @@
#include "wled.h"
#include "palettes.h"
#define JSON_PATH_STATE 1
#define JSON_PATH_INFO 2
#define JSON_PATH_STATE_INFO 3
@@ -689,6 +687,20 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
}
}
/**
* @brief Populate a JSON object with device and runtime information.
*
* Fills the provided JsonObject with system, hardware, network, and LED subsystem
* metadata used by the JSON API (version, build, LED counts and capabilities,
* palettes and modes counts, WiFi and filesystem stats, uptime, time, and usermod
* additions).
*
* The function writes several nested objects and arrays (for example "leds",
* "wifi", "fs", "maps") and a set of top-level fields consumed by clients.
*
* @param root JsonObject to populate. Must be a valid writable JSON object;
* the function will create nested objects/arrays inside it.
*/
void serializeInfo(JsonObject root)
{
root[F("ver")] = versionString;
@@ -770,7 +782,8 @@ void serializeInfo(JsonObject root)
root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = getPaletteCount();
root[F("cpalcount")] = customPalettes.size(); //number of custom palettes
root[F("cpalcount")] = customPalettes.size(); // number of custom palettes
root[F("cpalmax")] = WLED_MAX_CUSTOM_PALETTES; // maximum number of custom palettes
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {

View File

@@ -210,7 +210,24 @@ void releaseJSONBufferLock()
// extracts effect mode (or palette) name from names serialized string
// caller must provide large enough buffer for name (including SR extensions)!
/**
* @brief Extracts the display name for a mode or palette into a caller-provided buffer.
*
* When src is JSON_mode_names or nullptr, the name is read from the built-in mode data
* (strip.getModeData). When src is JSON_palette_names and the mode index refers to a
* custom palette (mode > 255 - customPalettes.size()), a formatted "~ Custom N ~"
* name is written. Otherwise, the function parses a PROGMEM JSON-like string pointed
* to by src to locate the mode's quoted name (handles commas and quoted fields) and
* stops if an SR-extension marker '@' is encountered for that mode.
*
* The function always NUL-terminates dest and will truncate the name to fit maxLen.
*
* @param mode Index of the mode or palette to extract.
* @param src PROGMEM string source to parse, or JSON_mode_names / JSON_palette_names / nullptr.
* @param dest Caller-provided buffer to receive the NUL-terminated name (must be large enough).
* @param maxLen Maximum number of bytes to write into dest (including the terminating NUL).
* @return uint8_t Length of the resulting string written into dest (excluding the terminating NUL).
*/
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen)
{
if (src == JSON_mode_names || src == nullptr) {
@@ -230,7 +247,7 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
} else return 0;
}
if (src == JSON_palette_names && mode > (GRADIENT_PALETTE_COUNT + 13)) {
if (src == JSON_palette_names && mode > 255-customPalettes.size()) {
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
dest[maxLen-1] = '\0';
return strlen(dest);

View File

@@ -244,6 +244,24 @@ static bool captivePortal(AsyncWebServerRequest *request)
return false;
}
/**
* @brief Initialize and configure the HTTP server routes and handlers.
*
* Registers CORS/default headers and all web endpoints used by the device web UI and API,
* including static content routes, settings UI, JSON API (/json), file upload (/upload),
* OTA update endpoints (/update), optional pages (DMX, PixArt, PxMagic, CPAL, live views),
* WebSocket attachment, captive portal handling and a NotFound handler that routes API calls
* or serves a 404 page. Also installs the filesystem editor route (or an Access Denied
* stub) via createEditHandler and attaches an AsyncJsonWebHandler for JSON POSTs.
*
* Side effects:
* - Adds default HTTP headers (CORS).
* - Registers many server routes and their callbacks with global state handlers.
* - May set flags such as doReboot and configNeedsWrite from request handlers.
* - Enforces PIN/OTA lock and subnet restrictions inside sensitive endpoints (OTA, settings, cfg).
*
* This function does not return a value.
*/
void initServer()
{
//CORS compatiblity
@@ -470,29 +488,31 @@ void initServer()
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (captivePortal(request)) return;
if (!showWelcomePage || request->hasArg(F("sliders"))) {
handleStaticContent(request, F("/index.htm"), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_index, PAGE_index_L);
handleStaticContent(request, F("/index.htm"), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_index, PAGE_index_length);
} else {
serveSettings(request);
}
});
#ifdef WLED_ENABLE_PIXART
#ifndef WLED_DISABLE_2D
#ifdef WLED_ENABLE_PIXART
static const char _pixart_htm[] PROGMEM = "/pixart.htm";
server.on(_pixart_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixart, PAGE_pixart_L);
handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixart, PAGE_pixart_length);
});
#endif
#endif
#ifndef WLED_DISABLE_PXMAGIC
#ifndef WLED_DISABLE_PXMAGIC
static const char _pxmagic_htm[] PROGMEM = "/pxmagic.htm";
server.on(_pxmagic_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pxmagic, PAGE_pxmagic_L);
handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pxmagic, PAGE_pxmagic_length);
});
#endif
#endif
static const char _cpal_htm[] PROGMEM = "/cpal.htm";
server.on(_cpal_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_cpal, PAGE_cpal_L);
handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_cpal, PAGE_cpal_length);
});
#ifdef WLED_ENABLE_WEBSOCKETS