Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/moonbase/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Shows which other MoonLight devices are present in your local network.

* Device name: name of this device (set the name in the [WiFi station module](https://moonmodules.org/MoonLight/network/sta/))
* Devices: Devices found on the network
* Click on the name to go to the device (controls module) via mDNS
* Click on IP to go to the device in a new window
* Click on the name to go to the device via mDNS
* Click on IP to go to the device via it's IP address
Comment on lines +9 to +10
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix possessive “its”.

“it’s” → “its”.

✏️ Proposed fix
-  * Click on IP to go to the device via it's IP address
+  * Click on IP to go to the device via its IP address
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* Click on the name to go to the device via mDNS
* Click on IP to go to the device via it's IP address
* Click on the name to go to the device via mDNS
* Click on IP to go to the device via its IP address
🤖 Prompt for AI Agents
In `@docs/moonbase/devices.md` around lines 9 - 10, The possessive "it's" is
incorrect in the device instructions; update the text "Click on IP to go to the
device via it's IP address" to use the possessive form "its" instead. Locate the
sentence in docs/moonbase/devices.md (the line containing "Click on IP to go to
the device via it's IP address") and replace "it's" with "its" so it reads
"Click on IP to go to the device via its IP address".


* The functionality of this module will also be available in [ESP32 Devices](https://github.com/ewowi/ESP32Devices). ESP32 Devices is a MacOS and Windows application. 🚧
55 changes: 48 additions & 7 deletions interface/src/lib/components/moonbase/FieldRenderer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
// Send immediately on first interaction
onChange(event);
pendingSliderEvent = null;

throttleTimer = setTimeout(() => {
if (pendingSliderEvent) {
onChange(pendingSliderEvent);
Expand Down Expand Up @@ -128,6 +128,33 @@
//precent onClick when dblClick
let clickTimeout: any = null;
let preventClick = false;

// inspired by WLED
function genPalPrev(hexString: string) {
if (!hexString) return '';

// Convert hex string to byte array
const paletteData = [];
for (let i = 0; i < hexString.length; i += 2) {
paletteData.push(parseInt(hexString.substr(i, 2), 16));
}

// Parse palette data: groups of 4 bytes [index, r, g, b]
const gradient = [];
for (let i = 0; i < paletteData.length; i += 4) {
const index = paletteData[i];
const r = paletteData[i + 1];
const g = paletteData[i + 2];
const b = paletteData[i + 3];

// Convert index from 0-255 to percentage
const percent = Math.round((index / 255) * 100);

gradient.push(`rgb(${r},${g},${b}) ${percent}%`);
}

return `background: linear-gradient(to right,${gradient.join(',')});`;
}
</script>

<div class="flex-row flex items-center space-x-2 {!noPrompts ? 'mb-1' : ''}">
Expand All @@ -138,11 +165,8 @@
{/if}

{#if property.ro}
{#if property.type == 'ip'}
<a href="http://{value}" target="_blank">{value}</a>
{:else if property.type == 'mDNSName'}
<a href="http://{value}.local/moonbase/module?group=moonlight&module=lightscontrol">{value}</a
>
{#if property.type == 'ip' || property.type == 'mDNSName'}
<a href="http://{value}">{value}</a>
{:else if property.type == 'time'}
<span>{getTimeAgo(value, currentTime)}</span>
{:else if property.type == 'coord3D' && value != null}
Expand All @@ -153,7 +177,6 @@
{/if}
{:else if property.type == 'select' || property.type == 'selectFile'}
<select bind:value on:change={onChange} class="select">
<slot></slot>
{#each property.values as value, index}
<option value={property.type == 'selectFile' ? value : index}>
{value}
Expand All @@ -163,6 +186,24 @@
{#if property.type == 'selectFile'}
<FileEditWidget path={value} showEditor={false} />
{/if}
{:else if property.type == 'palette'}
<div style="display: flex; gap: 8px; align-items: center;">
<select bind:value on:change={onChange} class="select">
{#each property.values as val, index}
<option value={index}>{val.name}</option>
{/each}
</select>
<div class="palette-preview" style={genPalPrev(property.values[value]?.colors)}></div>
</div>

<style>
.palette-preview {
width: 250px;
height: 40px;
border: 1px solid #ccc;
border-radius: 3px;
}
</style>
{:else if property.type == 'checkbox'}
<input
type="checkbox"
Expand Down
25,231 changes: 12,625 additions & 12,606 deletions lib/framework/WWWData.h

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions src/MoonLight/Modules/ModuleEffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ class ModuleEffects : public NodeManager {
addControlValue(control, getNameAndTags<AudioRingsEffect>());
addControlValue(control, getNameAndTags<LinesEffect>());
addControlValue(control, getNameAndTags<FireEffect>());
addControlValue(control, getNameAndTags<StarSkyEffect>());
addControlValue(control, getNameAndTags<FixedRectangleEffect>());
addControlValue(control, getNameAndTags<ParticlesEffect>());
addControlValue(control, getNameAndTags<PraxisEffect>());
addControlValue(control, getNameAndTags<StarSkyEffect>());
#if USE_M5UNIFIED
addControlValue(control, getNameAndTags<MoonManEffect>());
#endif
Expand Down Expand Up @@ -135,6 +135,10 @@ class ModuleEffects : public NodeManager {
addControlValue(control, getNameAndTags<RainEffect>());
addControlValue(control, getNameAndTags<TetrixEffect>());
addControlValue(control, getNameAndTags<WaverlyEffect>());
addControlValue(control, getNameAndTags<ColorTwinkleEffect>());
addControlValue(control, getNameAndTags<PlasmaEffect>());
addControlValue(control, getNameAndTags<JuliaEffect>());
addControlValue(control, getNameAndTags<PoliceEffect>());

// FastLED effects
addControlValue(control, getNameAndTags<RainbowEffect>());
Expand Down Expand Up @@ -176,12 +180,11 @@ class ModuleEffects : public NodeManager {
if (!node) node = checkAndAlloc<SolidEffect>(name);
if (!node) node = checkAndAlloc<AudioRingsEffect>(name);
if (!node) node = checkAndAlloc<FireEffect>(name);

if (!node) node = checkAndAlloc<StarSkyEffect>(name);
if (!node) node = checkAndAlloc<FixedRectangleEffect>(name);
if (!node) node = checkAndAlloc<FreqSawsEffect>(name);
if (!node) node = checkAndAlloc<LinesEffect>(name);
if (!node) node = checkAndAlloc<MarioTestEffect>(name);
if (!node) node = checkAndAlloc<StarSkyEffect>(name);
#if USE_M5UNIFIED
if (!node) node = checkAndAlloc<MoonManEffect>(name);
#endif
Expand Down Expand Up @@ -229,6 +232,10 @@ class ModuleEffects : public NodeManager {
if (!node) node = checkAndAlloc<RainEffect>(name);
if (!node) node = checkAndAlloc<TetrixEffect>(name);
if (!node) node = checkAndAlloc<WaverlyEffect>(name);
if (!node) node = checkAndAlloc<ColorTwinkleEffect>(name);
if (!node) node = checkAndAlloc<PlasmaEffect>(name);
if (!node) node = checkAndAlloc<JuliaEffect>(name);
if (!node) node = checkAndAlloc<PoliceEffect>(name);

// FastLED
if (!node) node = checkAndAlloc<RainbowEffect>(name);
Expand Down
93 changes: 79 additions & 14 deletions src/MoonLight/Modules/ModuleLightsControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "MoonBase/Module.h"
#include "MoonBase/Modules/FileManager.h"
#include "MoonBase/Utilities.h" //for isInPSRAM
#include "palettes.h"

// Convert ModuleLightsControl state -> Home Assistant JSON
void readMQTT(ModuleState& state, JsonObject& root) {
Expand Down Expand Up @@ -277,19 +278,81 @@ class ModuleLightsControl : public Module {
control = addControl(controls, "blue", "slider");
control["default"] = 255;
control["color"] = "Blue";
control = addControl(controls, "palette", "select");
control = addControl(controls, "palette", "palette"); // palette type
control["default"] = 9;
addControlValue(control, "Cloud");
addControlValue(control, "Lava");
addControlValue(control, "Ocean");
addControlValue(control, "Forest");
addControlValue(control, "Rainbow");
addControlValue(control, "RainbowStripe");
addControlValue(control, "Party");
addControlValue(control, "Heat");
addControlValue(control, "Random");
addControlValue(control, "MoonModules");
addControlValue(control, "Orange");

control["values"].to<JsonArray>();

const char* const builtInPaletteNames[] = {"Cloud", "Lava", "Ocean", "Forest", "Rainbow", "RainbowStripe", "Party", "Heat"};
const CRGBPalette16 builtInPalettes[] = {CloudColors_p, LavaColors_p, OceanColors_p, ForestColors_p, RainbowColors_p, RainbowStripeColors_p, PartyColors_p, HeatColors_p};
for (int i = 0; i < sizeof(builtInPaletteNames) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = builtInPaletteNames[i];

// Convert CRGBPalette16 to hex string
String hexString = "";
const CRGBPalette16 pal = builtInPalettes[i];

char buf[3];
for (int j = 0; j < 16; j++) {
// Add index (0, 16, 32, ... 240)
sprintf(buf, "%02x", j * 16);
hexString += buf;

// Add R, G, B
sprintf(buf, "%02x", pal[j].r);
hexString += buf;
sprintf(buf, "%02x", pal[j].g);
hexString += buf;
sprintf(buf, "%02x", pal[j].b);
hexString += buf;
}

// Add final entry at index 255
sprintf(buf, "%02x", 255);
hexString += buf;
sprintf(buf, "%02x", pal[15].r);
hexString += buf;
sprintf(buf, "%02x", pal[15].g);
hexString += buf;
sprintf(buf, "%02x", pal[15].b);
hexString += buf;

object["colors"] = hexString;
}

const char* const customPaletteNames[] = {"Random", "MoonModules", "Orange"};
for (int i = 0; i < sizeof(customPaletteNames) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = customPaletteNames[i];
object["colors"] = "";
}

// add palettes from palettes.h
for (int i = 0; i < sizeof(palette_names) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = palette_names[i];

String hexString = "";
const byte* palette = gGradientPalettes[i];
int j = 0;

// Read 4-byte entries (index, r, g, b) until index == 255
while (j < 100) { // Safety limit
for (int k = 0; k < 4; k++) {
char buf[3];
sprintf(buf, "%02x", palette[j++]);
hexString += buf;
}
// Check if we just wrote the final entry (index was 255)
if (palette[j - 4] == 255) break;
}

object["colors"] = hexString;
}
Comment on lines +281 to +355
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Palette preview generation can truncate larger gradients.

The while (j < 100) cap will cut off palettes longer than 100 bytes (some in palettes.h exceed that), producing incomplete previews. Consider raising the cap and guarding by 4‑byte entries.

🛠️ Suggested tweak
-      while (j < 100) {  // Safety limit
+      const size_t kMaxPaletteBytes = 256;  // allow larger gradients
+      while (j + 3 < kMaxPaletteBytes) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
control = addControl(controls, "palette", "palette"); // palette type
control["default"] = 9;
addControlValue(control, "Cloud");
addControlValue(control, "Lava");
addControlValue(control, "Ocean");
addControlValue(control, "Forest");
addControlValue(control, "Rainbow");
addControlValue(control, "RainbowStripe");
addControlValue(control, "Party");
addControlValue(control, "Heat");
addControlValue(control, "Random");
addControlValue(control, "MoonModules");
addControlValue(control, "Orange");
control["values"].to<JsonArray>();
const char* const builtInPaletteNames[] = {"Cloud", "Lava", "Ocean", "Forest", "Rainbow", "RainbowStripe", "Party", "Heat"};
const CRGBPalette16 builtInPalettes[] = {CloudColors_p, LavaColors_p, OceanColors_p, ForestColors_p, RainbowColors_p, RainbowStripeColors_p, PartyColors_p, HeatColors_p};
for (int i = 0; i < sizeof(builtInPaletteNames) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = builtInPaletteNames[i];
// Convert CRGBPalette16 to hex string
String hexString = "";
const CRGBPalette16 pal = builtInPalettes[i];
char buf[3];
for (int j = 0; j < 16; j++) {
// Add index (0, 16, 32, ... 240)
sprintf(buf, "%02x", j * 16);
hexString += buf;
// Add R, G, B
sprintf(buf, "%02x", pal[j].r);
hexString += buf;
sprintf(buf, "%02x", pal[j].g);
hexString += buf;
sprintf(buf, "%02x", pal[j].b);
hexString += buf;
}
// Add final entry at index 255
sprintf(buf, "%02x", 255);
hexString += buf;
sprintf(buf, "%02x", pal[15].r);
hexString += buf;
sprintf(buf, "%02x", pal[15].g);
hexString += buf;
sprintf(buf, "%02x", pal[15].b);
hexString += buf;
object["colors"] = hexString;
}
const char* const customPaletteNames[] = {"Random", "MoonModules", "Orange"};
for (int i = 0; i < sizeof(customPaletteNames) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = customPaletteNames[i];
object["colors"] = "";
}
// add palettes from palettes.h
for (int i = 0; i < sizeof(palette_names) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = palette_names[i];
String hexString = "";
const byte* palette = gGradientPalettes[i];
int j = 0;
// Read 4-byte entries (index, r, g, b) until index == 255
while (j < 100) { // Safety limit
for (int k = 0; k < 4; k++) {
char buf[3];
sprintf(buf, "%02x", palette[j++]);
hexString += buf;
}
// Check if we just wrote the final entry (index was 255)
if (palette[j - 4] == 255) break;
}
object["colors"] = hexString;
}
control = addControl(controls, "palette", "palette"); // palette type
control["default"] = 9;
control["values"].to<JsonArray>();
const char* const builtInPaletteNames[] = {"Cloud", "Lava", "Ocean", "Forest", "Rainbow", "RainbowStripe", "Party", "Heat"};
const CRGBPalette16 builtInPalettes[] = {CloudColors_p, LavaColors_p, OceanColors_p, ForestColors_p, RainbowColors_p, RainbowStripeColors_p, PartyColors_p, HeatColors_p};
for (int i = 0; i < sizeof(builtInPaletteNames) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = builtInPaletteNames[i];
// Convert CRGBPalette16 to hex string
String hexString = "";
const CRGBPalette16 pal = builtInPalettes[i];
char buf[3];
for (int j = 0; j < 16; j++) {
// Add index (0, 16, 32, ... 240)
sprintf(buf, "%02x", j * 16);
hexString += buf;
// Add R, G, B
sprintf(buf, "%02x", pal[j].r);
hexString += buf;
sprintf(buf, "%02x", pal[j].g);
hexString += buf;
sprintf(buf, "%02x", pal[j].b);
hexString += buf;
}
// Add final entry at index 255
sprintf(buf, "%02x", 255);
hexString += buf;
sprintf(buf, "%02x", pal[15].r);
hexString += buf;
sprintf(buf, "%02x", pal[15].g);
hexString += buf;
sprintf(buf, "%02x", pal[15].b);
hexString += buf;
object["colors"] = hexString;
}
const char* const customPaletteNames[] = {"Random", "MoonModules", "Orange"};
for (int i = 0; i < sizeof(customPaletteNames) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = customPaletteNames[i];
object["colors"] = "";
}
// add palettes from palettes.h
for (int i = 0; i < sizeof(palette_names) / sizeof(char*); i++) {
JsonArray values = control["values"];
JsonObject object = values.add<JsonObject>();
object["name"] = palette_names[i];
String hexString = "";
const byte* palette = gGradientPalettes[i];
int j = 0;
// Read 4-byte entries (index, r, g, b) until index == 255
const size_t kMaxPaletteBytes = 256; // allow larger gradients
while (j + 3 < kMaxPaletteBytes) {
for (int k = 0; k < 4; k++) {
char buf[3];
sprintf(buf, "%02x", palette[j++]);
hexString += buf;
}
// Check if we just wrote the final entry (index was 255)
if (palette[j - 4] == 255) break;
}
object["colors"] = hexString;
}
🤖 Prompt for AI Agents
In `@src/MoonLight/Modules/ModuleLightsControl.h` around lines 281 - 355, The loop
that builds hexString for gGradientPalettes uses a hard cap while (j < 100)
which can truncate palettes—replace this fixed limit with a safe bounds check
tied to the actual palette byte length (or a defined MAX_PALETTE_BYTES) and
iterate in 4-byte steps until you encounter an index==255 termination;
specifically update the loop that reads from gGradientPalettes[i] (the variables
palette and j) so it ensures (j + 4) <= palette_length (or <= MAX_PALETTE_BYTES)
before reading the next 4 bytes, append the 4 bytes per entry, and break when
palette[j] (the entry index) == 255 to avoid truncation.


control = addControl(controls, "preset", "pad");
control["width"] = 8;
Expand Down Expand Up @@ -322,7 +385,7 @@ class ModuleLightsControl : public Module {
layerP.lights.header.blue = _state.data["blue"];
} else if (updatedItem.name == "lightsOn" || updatedItem.name == "brightness") {
uint8_t newBri = _state.data["lightsOn"] ? _state.data["brightness"] : 0;
if (!!layerP.lights.header.brightness != !!newBri && pinRelayLightsOn != UINT8_MAX) {
if (!!layerP.lights.header.brightness != !!newBri && pinRelayLightsOn != UINT8_MAX) { // !! is intentional!
EXT_LOGD(ML_TAG, "pinRelayLightsOn %s", !!newBri ? "On" : "Off");
digitalWrite(pinRelayLightsOn, newBri > 0 ? HIGH : LOW);
};
Expand Down Expand Up @@ -359,7 +422,9 @@ class ModuleLightsControl : public Module {
layerP.palette[i] = CRGB(255, map(i, 0, nrOfPaletteEntries - 1, 0, 255), 0); // from red via orange to yellow
}
} else {
layerP.palette = PartyColors_p; // should never occur
byte tcp[76] = {255}; // WLEDMM: prevent out-of-range access in loadDynamicGradientPalette()
memcpy(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[updatedItem.value.as<uint8_t>() - 11])), 72);
layerP.palette.loadDynamicGradientPalette(tcp);
Comment on lines +425 to +427
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find files related to palette handling
fd -t f "\.h$|\.cpp$" | xargs rg -l "loadDynamicGradientPalette|gGradientPalettes" | head -20

Repository: MoonModules/MoonLight

Length of output: 141


🏁 Script executed:

# Search for loadDynamicGradientPalette implementation details
rg -A 10 "loadDynamicGradientPalette" --type cpp --type h

Repository: MoonModules/MoonLight

Length of output: 1361


🏁 Script executed:

# Check gradient palette definitions and sizes
rg -B 2 -A 5 "gGradientPalettes" --type cpp --type h | head -50

Repository: MoonModules/MoonLight

Length of output: 2130


🏁 Script executed:

# Look for FastLED palette structure in the codebase
rg "CRGBPalette" --type cpp --type h | head -20

Repository: MoonModules/MoonLight

Length of output: 546


🏁 Script executed:

# Read the palettes.h file to see palette definitions
cat src/MoonLight/Modules/palettes.h | head -100

Repository: MoonModules/MoonLight

Length of output: 4786


🏁 Script executed:

# Search for the actual gradient palette definitions
rg -A 2 "gp =" src/MoonLight/Modules/palettes.h | head -40

Repository: MoonModules/MoonLight

Length of output: 47


🏁 Script executed:

# Check what size gradients typically are by looking at their PROGMEM definitions
rg "PROGMEM|gp\[" src/MoonLight/Modules/palettes.h | head -20

Repository: MoonModules/MoonLight

Length of output: 2895


🏁 Script executed:

# Count the actual byte size of all palette definitions
python3 << 'EOF'
import re

with open('src/MoonLight/Modules/palettes.h', 'r') as f:
    content = f.read()

# Find all palette definitions
pattern = r'const byte (\w+)\[\] = \{([^}]+)\}'
matches = re.findall(pattern, content)

sizes = []
for name, data in matches:
    # Count commas to get byte count
    byte_count = len(data.split(','))
    sizes.append((name, byte_count))
    
# Sort by size
sizes.sort(key=lambda x: x[1], reverse=True)

print("Top 15 largest palettes:")
for name, size in sizes[:15]:
    print(f"  {name}: {size} bytes")
    
print(f"\nTotal palettes: {len(sizes)}")
print(f"Max size: {max(s[1] for s in sizes)} bytes")
print(f"Min size: {min(s[1] for s in sizes)} bytes")
EOF

Repository: MoonModules/MoonLight

Length of output: 151


🏁 Script executed:

# Check if there are any palettes >= 144 bytes
rg "const byte.*\[\] = \{" src/MoonLight/Modules/palettes.h | wc -l

Repository: MoonModules/MoonLight

Length of output: 67


🏁 Script executed:

# Look for the loadDynamicGradientPalette implementation in FastLED
rg -B 5 -A 15 "loadDynamicGradientPalette" --type h | head -60

Repository: MoonModules/MoonLight

Length of output: 2338


🏁 Script executed:

# Find the largest palette definitions
rg "const byte.*\[\] = \{" src/MoonLight/Modules/palettes.h -A 0 | while read line; do
  name=$(echo "$line" | sed 's/.*const byte \([a-zA-Z_]*\).*/\1/')
  # Extract just the declaration and count braces
  size=$(rg "const byte $name\[\] = \{[^}]+\}" src/MoonLight/Modules/palettes.h | grep -o "{[^}]*}" | head -1 | tr ',' '\n' | wc -l)
  echo "$size $name"
done | sort -rn | head -20

Repository: MoonModules/MoonLight

Length of output: 592


🏁 Script executed:

# Search for documented sizes in the file
rg "Size: [0-9]+ bytes" src/MoonLight/Modules/palettes.h | sed 's/.*Size: \([0-9]*\).*/\1/' | sort -rn | uniq

Repository: MoonModules/MoonLight

Length of output: 106


🏁 Script executed:

# Get specific info about the largest palettes mentioned
rg -B 3 "Size: [0-9]+ bytes" src/MoonLight/Modules/palettes.h | grep -E "(Size:|Gradient palette)" | paste - - | sort -t: -k2 -rn | head -20

Repository: MoonModules/MoonLight

Length of output: 108


🏁 Script executed:

# Find which palette is 144 bytes
rg -B 5 "Size: 144 bytes" src/MoonLight/Modules/palettes.h

Repository: MoonModules/MoonLight

Length of output: 351


🏁 Script executed:

# Get all palettes sorted by size to understand the distribution
rg -B 3 "Size: [0-9]+ bytes" src/MoonLight/Modules/palettes.h | rg "Size: [0-9]+" | sed 's/.*Size: \([0-9]*\).*/\1/' | sort -n | tail -20

Repository: MoonModules/MoonLight

Length of output: 125


🏁 Script executed:

# Search for FastLED palette loading code in the repository
rg -i "loadDynamicGradientPalette|gradient.*palette" --type h --type cpp | grep -i "load\|terminator\|255" | head -20

Repository: MoonModules/MoonLight

Length of output: 310


🏁 Script executed:

# Check the exact temperature_gp definition to understand its structure
rg -A 40 "Gradient palette.*temperature_gp" src/MoonLight/Modules/palettes.h

Repository: MoonModules/MoonLight

Length of output: 1995


Copy gradient palettes completely until terminator, not with fixed byte limit.

The current code copies only 72 bytes, which truncates the temperature_gp palette (144 bytes) and removes its terminator (0xFF entry), causing loadDynamicGradientPalette() to read out-of-bounds. The buffer initialization byte tcp[76] = {255} only protects position 0; the rest is zero-initialized, not a valid terminator. Repack with a copy-until-terminator loop using pgm_read_byte() to safely handle palettes of any size up to the buffer limit.

🤖 Prompt for AI Agents
In `@src/MoonLight/Modules/ModuleLightsControl.h` around lines 425 - 427, The
fixed-size memcpy truncates palettes and drops the 0xFF terminator; change to
copying from PROGMEM byte-by-byte using pgm_read_byte() from
gGradientPalettes[index] (index computed as updatedItem.value.as<uint8_t>() -
11) into tcp until you encounter 0xFF or reach the tcp buffer limit, then ensure
tcp contains a valid 0xFF terminator before calling
layerP.palette.loadDynamicGradientPalette(tcp); also ensure tcp’s size is
sufficient for the largest expected palette and set the final byte to 0xFF if
the loop exits due to the size limit.

}
// layerP.palette = LavaColors_p;
} else if (updatedItem.name == "preset") {
Expand Down
Loading