Skip to content

Commit 0fb224b

Browse files
authored
Merge pull request #131 from MoonModules/effects-palettes
Palettes and effects
2 parents 0726e5e + cdd2368 commit 0fb224b

16 files changed

Lines changed: 223 additions & 84 deletions

File tree

File renamed without changes.

misc/instructions/MoonLight.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
- Moonlight is a fork of Upstream sveltekit repo (theelims/ESP32-sveltekit):
2+
- /interface except moonbase folders (frontend)
3+
- /lib folder (backend)
4+
- Changes in upstream code has been commented with // 🌙 (moonbase) or // 💫 (moonlight)
5+
- Core benefits of upstream sveltekit: state and services with subscriptions and up and running svelte (reactive framework)
6+
- Improvements to the sveltekit stuff can be added, but always submitting a pull request to upstream needs to be considered. In theory all the // 🌙 are possible PR’s but for different reasons they not always end up in upstream
7+
- MoonLight UI is now pretty much inspired by the UI used in upstream. Future goals is to develop more
8+
- Repo is split in
9+
- moonbase (everything not lights related, see for instance env:esp32-d0-moonbase in esp32-d0.ini, compiles only moonbase, not moonlight. Can be the basis for any IOT project)
10+
- Moonlight: everything lights related
11+
- MoonBase / MoonLight
12+
- /src contains everything moonbase and moonlight specific
13+
- See /src/MoonBase: some files contains MoonLight specifics, e.g. Nodes.h/.cpp. This is work in progress, eventually things like Nodes should be generic also for non light applications.
14+
- pal.h and pal_espidf.cpp as a first attempt to come to an esp-idf only repository (not using arduino). This is very much work in progress.
15+
- Modules and nodes are the core concepts of MoonBase/MoonLight. Modules are fully generated frontend(UI)/backend(server) modules (no specific sveltekit code per module, see /interface/src/lib/components/moonbase and /interface/src/routes/moonbase for Module specific code)
16+
- Monitor module is now also lights specific, in the future should be a generic moonbase feature.
17+
- Lightscontrol is now also lights specific, in the future should be a generic module, maybe renamed to something more generic. Lightscontrol is the interface / API to the outside world. e.g. DMX, IR, Home assistant etc, all have the variables / rest api / web sockets / … of lights control as their interface point
18+
- MoonLight core concepts are physical and virtual layer. The physical layer has a double buffered / two array called channelsE/D: channels for effects and channels for drivers. Main.cpp manages the double buffering / producer/consumer synchronization of the two arrays in a way 2 core MCU’s like the ESP32 series uses one core for the producer (effects) and 1 core for the consumer (drivers)
19+
- The channel array(s) are a generalization of FastLED’s CRGB leds. In FastLED’s, each entry is one CRGB object. In Moonlight each entry is one light with a nrOfChannels. So RGB lights have 3 nrOfChannels, RGBW 4, but other lights like moving heads are also supported (can be up to 24/32/… lights)
20+
- The effects module contains effect nodes and modifier nodes, the Driver module contains layout and driver nodes.
21+
- Eventually the effects and drivers module will be replaced / augmented by a graphical interface showing nodes, connected to other nodes in different lanes (e.g. physical lane and multiple virtual layer lanes)
22+
- MoonLight currently supports audio reactivity by using https://github.com/netmindz/WLED-sync, depending on a WLED-MM device transmitting audio over the network. Implementing FastLED audio is work in progress, WLED-MM audio is currently moved to a separate library and in the future will also be added as an audio node. These 2 options allow for Audio processing indepenent of another device running WLED.
23+
- MoonLight documentation is maintained in the same repo and is deployed here: https://moonmodules.org/MoonLight/
24+
- See architecture : https://moonmodules.org/MoonLight/develop/architecture/ for more details on system tasks, producer/consumer double bufering etc.
25+
- /src/MoonLight/Nodes contain subfolders for all currently supported Effects, Modifiers, Driver and layout nodes. Each node is a class with constructor and destructor to manage all their memory usage (after deleting a node in the effects or drivers module, they should be completely reverse their resource claims). Nodes have controls, each node can define their own unique controls and is shown and managed in the UI.
26+
- MoonLight supports different LED drivers , the FastLED channels api drivers and also physical led driver which is based on I2SClocklessDriver repo for non P4 and on parlio.h/cpp for P4. They can be used as alternatives.
27+
- All pins are manager by the IO module by board presets. Board presets is the only place where pins are defined. Nodes can use the pin definitions (and can report back to the IO module). Currently the IO module supports also I2C, in the future I2S, SPi, … will also be supported.
28+
- Live scripts (https://github.com/hpwit/ESPLiveScript.git) is supported, now MoonLight specific but should be moonbase functionality eventually. It allows for live coding of nodes. Currently effect and layout nodes are implemented but not tested for a while (due to some crashes and planned further development of that repo).
29+
- General
30+
- MoonLight should run on all ESP32 devices. Mainly used and tested now on ESP32-D0 (standard dev MCU’s), ESP32-S3 and ESP32-P4. Running MoonLight on ESP32-D0 is a challenge as heap is pretty much used up, and Flash size is limited. We currently use a 3MB flash partition so 4MB boards cannot do OTA at the moment. In the future we plan to implement a minimal OTA partition (as investigated at Tasmota/ESPHome …), so also small flash size boards (4MB) can do OTA.
31+
- Optimize on the critical process: running effects / modifiers (producers) and displaying them on leds (direct via pins on the board using led drivers or via Art-Net over the network). So code for the critical process should compile to minimal assembler code and checks on code should be minimal. (Example use int instead of unsigned int as no need to control if it is negative if the developer should ensure it is possible, or do not check if a const char * is null as it’s the developer responsibility to make sure this is the case). In short: the code should not be resilient to developer mistakes, the developer must solve the mistakes.
32+
- Non critical processes can run in reasonable time, in general 20ms response time for UI is more than enough.
33+
- See also GIMINI.md in /misc/parking for info about upstream sveltekit

platformio.ini

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ build_flags =
5757
-D BUILD_TARGET=\"$PIOENV\"
5858
-D APP_NAME=\"MoonLight\" ; 🌙 Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
5959
-D APP_VERSION=\"0.8.1\" ; semver compatible version string
60-
-D APP_DATE=\"20260224\" ; 🌙
60+
-D APP_DATE=\"20260301\" ; 🌙
6161

6262
-D PLATFORM_VERSION=\"pioarduino-55.03.37\" ; 🌙 make sure it matches with above plaftform
6363

@@ -204,9 +204,20 @@ build_flags =
204204
-D DRIVERS_STACK_SIZE=6144 ; psramFound() ? 4 * 1024 : 3 * 1024, 4096 is sufficient for now. Update: due to FastLED audio I had to increase to 6144 (might want to move audio to a separate task)
205205

206206
; -D FASTLED_TESTING ; causes duplicate definition of initSpiHardware(); - workaround: removed implementation in spi_hw_manager_esp32.cpp.hpp
207-
-D FASTLED_BUILD=\"20260224\"
207+
-D FASTLED_BUILD=\"20260226\"
208208
lib_deps =
209-
https://github.com/FastLED/FastLED#56d49b14796fa14e4fcc3f394256ae0bbe509b65 ; master 20260224
209+
https://github.com/FastLED/FastLED#809744a9738f7e390f345d597ea59700648de906 ;
210+
; https://github.com/FastLED/FastLED#7e133f8f39480cac7ca5876fc78c980f874b527d ; 20260225 10:03 AM bass/mid/high looks nervous / all or nothing, doens't align with audio sweep (https://www.youtube.com/watch?v=PAsMlDptjx8)
211+
; https://github.com/FastLED/FastLED#5d7ce6f1fcd1cf3cccdc641636c66dd46ade44fc ; 20260224 9:16 PM FastLED driver websocket issue
212+
; https://github.com/FastLED/FastLED#8adee9130ea1849da6f9d01578171016e69f18c1 ; 20260224 8:35 PM FastLED driver websocket issue
213+
; https://github.com/FastLED/FastLED#56d49b14796fa14e4fcc3f394256ae0bbe509b65 ; master 20260224 8:49 AM (fix fl_* functions) ; FastLED driver websocket issue
214+
; https://github.com/FastLED/FastLED#6017ce6b94552fd57f5c0be60592902d8111b470 ; 20260224 5:15 fix c6... not working ❌
215+
; https://github.com/FastLED/FastLED#629a1e468fd823a6834c1bd6430fedea522089ed ; 20260224 4:17 fix spam ... compile error
216+
; https://github.com/FastLED/FastLED#8c0015af87908cb641907df69061321f6a306f0e ; 20260224 4:15 Channel engine ... compile error
217+
; https://github.com/FastLED/FastLED#4cef007214bdf70039576ccf60c9384981c06a0d ; 20260224 4:13 isqrt32 ... working ✅
218+
; https://github.com/FastLED/FastLED#595150340cb0034b50328bee8eaaaceaeb570dc7 ; 20260224 2:48 AM add filtering to audio detectors working
219+
; https://github.com/FastLED/FastLED#d03ffd69c68f1a00f883243f78b2a0e9bfb66298 ; master 2026023 6:15AM (fix no preamble for the UCS led chipset) websocket working!
220+
210221
https://github.com/ewowi/WLED-sync#25f280b5e8e47e49a95282d0b78a5ce5301af4fe ; sourceIP + fftUdp.clear() if arduino >=3 (20251104)
211222

212223
; 💫 currently only enabled on s3 as esp32dev runs over 100%

src/MoonBase/Modules/ModuleIO.h

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -675,36 +675,47 @@ class ModuleIO : public Module {
675675
// on update triggers another onUpdates on 2 occasions: 1) newState modded (directly) and 2) setBoardPresetDefaults (via main loop)
676676
// each will trigger the updateHandler of this module sending readpins again ...
677677
void onUpdate(const UpdatedItem& updatedItem, const String& originId) override {
678+
// Below updates only triggered from UI (not from backend updates)
679+
if (!originId.toInt()) return;
680+
678681
JsonDocument doc;
679682
JsonObject newState = doc.to<JsonObject>();
683+
684+
// Handle boardPreset changes
680685
if (updatedItem.name == "boardPreset") {
681-
// if booting and modded is false or ! booting
682-
if ((updatedItem.oldValue == "" && _state.data["modded"] == false) || updatedItem.oldValue != "") { // only update unmodded
686+
// Only load board defaults if modded is false
687+
if (_state.data["modded"] == false) {
683688
EXT_LOGD(MB_TAG, "newBoardID %s %s[%d]%s[%d].%s = %s -> %s", originId.c_str(), updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(), updatedItem.oldValue.c_str(), updatedItem.value.as<String>().c_str());
684-
newBoardID = updatedItem.value; // run in sveltekit task
689+
newBoardID = updatedItem.value; // Will be processed in loop20ms
690+
} else {
691+
EXT_LOGD(MB_TAG, "boardPreset change ignored - modded=true");
685692
}
693+
return; // Don't process further for boardPreset changes
686694
}
687695

688-
if (!originId.toInt()) return; // below updates only triggered from UI
689-
690696
if (updatedItem.name == "modded") {
691-
// set pins to default if modded is turned off
697+
// When modded is set to false, reload the current board preset defaults
692698
if (updatedItem.value == false) {
693-
// EXT_LOGD(MB_TAG, "%s[%d]%s[%d].%s = %s -> %s", updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(), updatedItem.oldValue.c_str(), updatedItem.value.as<String>().c_str());
694-
newBoardID = _state.data["boardPreset"]; // run in sveltekit task
699+
EXT_LOGD(MB_TAG, "modded set to false - reloading board defaults");
700+
newBoardID = _state.data["boardPreset"]; // Reload current board
695701
}
696702
} else if (updatedItem.name == "switch1" || updatedItem.name == "switch2") {
697-
// rebuild with new switch setting
698-
newBoardID = _state.data["boardPreset"]; // run in sveltekit task
703+
// Rebuild pins with new switch settings
704+
EXT_LOGD(MB_TAG, "switch changed - rebuilding board defaults");
705+
newBoardID = _state.data["boardPreset"];
699706
} else if (updatedItem.name == "maxPower") {
707+
// Manual maxPower change = user is customizing
700708
newState["modded"] = true;
701-
} else if (updatedItem.name == "usage" || updatedItem.name == "index") { // usage / index of any of the pins
709+
} else if (updatedItem.name == "usage" || updatedItem.name == "index") {
710+
// Manual pin usage change = user is customizing
702711
newState["modded"] = true;
703712
} else if (updatedItem.name == "i2cFreq") {
704-
Wire.setClock(updatedItem.value.as<uint32_t>() * 1000); // uint32_t instead of uint16_t to multiply in 32-bit arithmetic
713+
Wire.setClock(updatedItem.value.as<uint32_t>() * 1000);
705714
}
706715

707-
if (newState.size()) update(newState, ModuleState::update, _moduleName); // if changes made then update
716+
if (newState.size()) {
717+
update(newState, ModuleState::update, _moduleName);
718+
}
708719
}
709720

710721
// Function to convert drive capability to string
@@ -723,26 +734,35 @@ class ModuleIO : public Module {
723734
}
724735
}
725736

726-
bool _initialPinReadDone = false;
737+
bool _initialUpdateDone = false;
727738

728739
void loop20ms() override {
729740
// run in sveltekit task
730741
Module::loop20ms();
731742

732-
// update board presets
743+
// Update board presets
733744
if (newBoardID != UINT8_MAX) {
734-
setBoardPresetDefaults(newBoardID); // run from sveltekit task
745+
setBoardPresetDefaults(newBoardID);
735746
newBoardID = UINT8_MAX;
736-
_initialPinReadDone = true;
747+
_initialUpdateDone = true;
737748
}
738749

739-
// during boot, the IO module is unchanged, not triggering updates, so need to do it manually
740-
if (!_initialPinReadDone) {
741-
callUpdateHandlers(_moduleName); // calls readPins for all subscribed handlers
742-
_initialPinReadDone = true;
750+
// During boot, handle boardPreset from file if modded=false
751+
// This runs AFTER file load completes and all values (including modded) are restored
752+
if (!_initialUpdateDone) {
753+
if (_state.data["modded"] == false) {
754+
uint8_t boardPreset = _state.data["boardPreset"];
755+
EXT_LOGD(MB_TAG, "Applying board preset %d defaults from file (modded=false)", boardPreset);
756+
newBoardID = boardPreset;
757+
// Will be processed in next loop20ms iteration at top, setting _initialUpdateDone
758+
} else {
759+
EXT_LOGD(MB_TAG, "Skipping board preset defaults - using custom pins from file (modded=true)");
760+
callUpdateHandlers(_moduleName);
761+
_initialUpdateDone = true;
762+
}
743763
}
744764

745-
// update I2C devices
765+
// Update I2C devices
746766
if (_triggerUpdateI2C != UINT8_MAX) {
747767
_updateI2CDevices();
748768
_triggerUpdateI2C = UINT8_MAX;

src/MoonBase/NodeManager.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ class NodeManager : public Module {
259259
// EXT_LOGD(ML_TAG, "handle %s[%d]%s[%d].%s = %s -> %s", updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(),
260260
// updatedItem.oldValue.c_str(), updatedItem.value.as<String>().c_str());
261261
if (updatedItem.index[0] < nodes->size()) {
262-
EXT_LOGD(ML_TAG, "%s on: %s (#%d)", nodeState["name"].as<const char*>(), updatedItem.value.as<String>().c_str(), nodes->size());
262+
const char* name = nodeState["name"];
263+
EXT_LOGD(ML_TAG, "%s on: %s (#%d)", name ? name : "", updatedItem.value.as<String>().c_str(), nodes->size());
263264
Node* nodeClass = (*nodes)[updatedItem.index[0]];
264265
if (nodeClass != nullptr) {
265266
nodeClass->on = updatedItem.value.as<bool>(); // set nodeclass on/off
@@ -269,7 +270,7 @@ class NodeManager : public Module {
269270
xSemaphoreGive(*nodeClass->layerMutex);
270271
nodeClass->requestMappings();
271272
} else
272-
EXT_LOGW(ML_TAG, "Nodeclass %s not found", nodeState["name"].as<const char*>());
273+
EXT_LOGW(ML_TAG, "Nodeclass %s not found", name ? name : "");
273274
}
274275
} // nodes[i].on
275276

@@ -287,8 +288,10 @@ class NodeManager : public Module {
287288
xSemaphoreGive(*nodeClass->layerMutex);
288289

289290
nodeClass->requestMappings();
290-
} else
291-
EXT_LOGW(ML_TAG, "nodeClass not found %s", nodeState["name"].as<const char*>());
291+
} else {
292+
const char* name = nodeState["name"];
293+
EXT_LOGW(ML_TAG, "nodeClass not found %s", name ? name : "");
294+
}
292295
}
293296

294297
} // nodes[i].controls[j].value

src/MoonLight/Modules/ModuleLightsControl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ class ModuleLightsControl : public Module {
338338
};
339339
layerP.lights.header.brightness = newBri;
340340
} else if (updatedItem.name == "palette") {
341-
const size_t nrOfPaletteEntries = sizeof(layerP.palette.entries) / sizeof(CRGB);
341+
// const size_t nrOfPaletteEntries = sizeof(layerP.palette.entries) / sizeof(CRGB);
342342
layerP.palette = getGradientPalette(updatedItem.value);
343343
} else if (updatedItem.name == "preset") {
344344
// copy /.config/effects.json to the hidden folder /.config/presets/preset[x].json

0 commit comments

Comments
 (0)