Skip to content
Merged
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
55 changes: 27 additions & 28 deletions docs/develop/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,30 @@ MoonLight uses a multi-core, multi-task architecture on ESP32 to achieve smooth
|------|------|----------|------------|-----------|---------|
| **WiFi/BT** | 0 (PRO_CPU) | 23 | System | Event-driven | System networking stack |
| **lwIP TCP/IP** | 0 (PRO_CPU) | 18 | System | Event-driven | TCP/IP protocol processing |
| **Effect Task** | 0 (PRO_CPU) | 10 | 3-4KB | ~60 fps | Calculate LED colors and effects |
| **ESP32SvelteKit** | 1 (APP_CPU) | 2 | System | 10ms | HTTP/WebSocket UI framework |
| **Driver Task** | 1 (APP_CPU) | 3 | 3-4KB | ~60 fps | Output data to LEDs via DMA/I2S/LCD/PARLIO |
| **ESP32SvelteKit** | 0 (PRO_CPU) | 2 | System | 10ms | HTTP/WebSocket UI framework |
| **Driver Task** | 0 (PRO_CPU) | 3 | 3-4KB | ~60 fps | Output data to LEDs via DMA/I2S/LCD/PARLIO |
| **Effect Task** | 1 (APP_CPU) | 3 | 3-4KB | ~60 fps | Calculate LED colors and effects |

Effect Task (Core 0, Priority 10)
Effect Task (Core 1, Priority 3)

- **Function**: Pure computation - calculates pixel colors based on effect algorithms
- **Operations**: Reads/writes to `channels` array, performs mathematical calculations
- **Tolerant to preemption**: WiFi interruptions are acceptable as this is non-timing-critical
- **Why Core 0**: Can coexist with WiFi; uses idle CPU cycles when WiFi is not transmitting
- Parking as currently experimenting with running on Core 1!! **Why Core 0**: Can coexist with WiFi; uses idle CPU cycles when WiFi is not transmitting

Driver Task (Core 1, Priority 3)
Driver Task (Core 0, Priority 3)

- **Function**: Timing-critical hardware operations
- **Operations**: Sends pixel data to LEDs via DMA, I2S (ESP32), LCD (S3), or PARLIO (P4)
- **Requires uninterrupted execution**: DMA timing must be precise to avoid LED glitches
- **Why Core 1**: Isolated from WiFi interference; WiFi on Core 0 cannot preempt this task
- Parking as currently experimenting with running on Core 0!! **Why Core 1**: Isolated from WiFi interference; WiFi on Core 0 cannot preempt this task

ESP32SvelteKit Task (Core 1, Priority 2)
ESP32SvelteKit Task (Core 0, Priority 2)

- **Function**: HTTP server and WebSocket handler for UI
- **Operations**: Processes REST API calls, WebSocket messages, JSON serialization
- **Runs every**: 10ms
- **Why Core 1, Priority 2**: Lower priority than Driver Task, so LED output always takes precedence
- **Why Core 0, Priority 2**: Lower priority than system Tasks

## Task Interaction Flow

Expand Down Expand Up @@ -293,6 +293,7 @@ if (psramFound()) {
} else {
lights.useDoubleBuffer = false;
lights.channelsE = allocMB<uint8_t>(maxChannels);
lights.channelsD = lights.channelsE;
}
```

Expand All @@ -313,25 +314,23 @@ Or in code before including framework:
Task Creation

```cpp
// Effect Task on Core 0
xTaskCreateUniversal(effectTask,
"AppEffectTask",
psramFound() ? 4 * 1024 : 3 * 1024,
NULL,
10, // Priority
&effectTaskHandle,
0 // Core 0 (PRO_CPU)
);

// Driver Task on Core 1
xTaskCreateUniversal(driverTask,
"AppDriverTask",
psramFound() ? 4 * 1024 : 3 * 1024,
NULL,
3, // Priority
&driverTaskHandle,
1 // Core 1 (APP_CPU)
);
xTaskCreateUniversal(effectTask, // task function
"AppEffects", // name
psramFound() ? 4 * 1024 : 3 * 1024, // stack size, save every byte on small devices
NULL, // parameter
3, // priority
&effectTaskHandle, // task handle
1 // application core. high speed effect processing
);

xTaskCreateUniversal(driverTask, // task function
"AppDrivers", // name
psramFound() ? 4 * 1024 : 3 * 1024, // stack size, save every byte on small devices
NULL, // parameter
3, // priority
&driverTaskHandle, // task handle
0 // protocol core: ideal for Art-Net, no issues encountered yet for LED drivers (pre-empt by WiFi ...)
);
```

## Summary
Expand Down
5 changes: 3 additions & 2 deletions docs/moonbase/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ Shows system tasks info.

| Task name (runtime) | Kconfig option | Default stack size (words → bytes) | Notes |
|----------------------|----------------|------------------------------------:|-------|
| AppEffectTask ||(psramFound()?6:4) * 1024||
| AppDriverTask ||(psramFound()?4:3) * 1024||
| AppEffects ||(psramFound()?6:4) * 1024|Core 1|
| AppDrivers ||(psramFound()?4:3) * 1024|Core 0|
| Sveltekit ||(psramFound()?8:6) * 1024|Core 0|
| **wifi** (Wi-Fi driver) | `CONFIG_ESP_WIFI_TASK_STACK_SIZE` | 3072 words → 12288 bytes → 12 KB | Handles Wi-Fi MAC/driver. Needs extra headroom. |
| **tiT** (TCP/IP / lwIP task) | `CONFIG_TCPIP_TASK_STACK_SIZE` | 3072 words → 12288 bytes → 12 KB | All socket callbacks, DHCP, etc. |
| **event** (ESP event loop) | `CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE` (older) / `CONFIG_ESP_EVENT_LOOP_TASK_STACK_SIZE` (newer) | 2048 words → 8192 bytes → 8 KB | Dispatches Wi-Fi/IP/BLE events. |
Expand Down
54 changes: 40 additions & 14 deletions interface/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,58 @@
<div class="card-body w-80">
<h2 class="card-title text-center text-2xl">Welcome to MoonLight</h2>
<p class="py-6 text-center">
Effect controller for direct and networked LEDs and lights.
We can be found on
<b>Effect controller for direct and networked LEDs and lights. By MoonModules.</b>
</p>
<span>
1 Setup <a class="btn btn-primary" href="/wifi/sta">WiFi</a>
</span>
<span>
2 Select a board preset in
<a class="btn btn-primary" href="/moonbase/module?group=moonlight&module=inputoutput">IO</a>
</span>
<span>
3 Add Layout & Driver
<a class="btn btn-primary" href="/moonbase/module?group=moonlight&module=drivers">Drivers</a
>
</span>
<span>
4 Add
<a class="btn btn-primary" href="/moonbase/module?group=moonlight&module=effects">Effects</a
>
</span>
<span>
🛟
<a
href="https://moonmodules.github.io/MoonLight"
class="link"
target="_blank"
rel="noopener noreferrer">Help</a
>
</span>
<span>
👀
<a
href="https://discord.gg/TC8NSUSCdV"
class="link"
target="_blank"
rel="noopener noreferrer">Discord WLED MM 2D & Audio</a
>,
rel="noopener noreferrer">Discord</a
>
<a
href="https://moonmodules.org"
href="https://www.youtube.com/@MoonModulesLighting"
class="link"
target="_blank"
rel="noopener noreferrer">MoonModules.org</a
> and
rel="noopener noreferrer">YouTube</a
>
<a
href="https://reddit.com/r/MoonModules"
class="link"
target="_blank"
rel="noopener noreferrer">Reddit</a
>.
</p>
<a
class="btn btn-primary"
href="/system/status"
onclick={() => notifications.success('You did it!', 1000)}>Start</a
>
>
<a href="https://moonmodules.org" class="link" target="_blank" rel="noopener noreferrer"
>MoonModules.org</a
>
</span>
</div>
</div>
</div>
31 changes: 16 additions & 15 deletions interface/src/routes/moonbase/monitor/Monitor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
};

const handleMonitor = (data: Uint8Array) => {
if (data.length == 37)
const headerPrimeNumber = 41;
if (data.length == headerPrimeNumber)
//see ModuleLightsControl.h:243
handleHeader(data);
else {
Expand Down Expand Up @@ -68,26 +69,26 @@

// let isPositions:number = header[6];
isPositions = true; //(header[6] >> 0) & 0x3; // bits 0-1
// offsetRed = (header[6] >> 2) & 0x3; // bits 2-3
// offsetGreen = (header[6] >> 4) & 0x3; // bits 4-5
// offsetBlue = (header[6] >> 6) & 0x3; // bits 6-7
// offsetWhite = header[13];

nrOfLights = view.getUint16(12, true);
channelsPerLight = view.getUint8(19);
offsetRGB = view.getUint8(20);
offsetWhite = view.getUint8(21);
nrOfChannels = view.getUint16(32, true);
lightPreset = view.getUint8(34);
// offsetRed = (header[27] >> 2) & 0x3; // bits 2-3
// offsetGreen = (header[27] >> 4) & 0x3; // bits 4-5
// offsetBlue = (header[27] >> 6) & 0x3; // bits 6-7
// offsetWhite = view.getUint8(28);

nrOfLights = view.getUint32(12, true);
nrOfChannels = view.getUint32(16, true);
lightPreset = view.getUint8(20);
channelsPerLight = view.getUint8(21);
offsetRGB = view.getUint8(26);
offsetWhite = view.getUint8(28);

//rebuild scene
createScene(el);

// let ledFactor: number = 1;//header[1];
// let ledSize: number = header[23];
width = view.getInt32(0, true); //header[0] + 256 * header[1];
height = view.getInt32(4, true); //header[4] + 256 * header[5];;
depth = view.getInt32(8, true); //header[8] + 256 * header[9];;
width = view.getInt32(0, true);
height = view.getInt32(4, true);
depth = view.getInt32(8, true);

setMatrixDimensions(width, height);

Expand Down
15 changes: 7 additions & 8 deletions lib/framework/ESP32SvelteKit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@ void ESP32SvelteKit::begin()
// Start the loop task
ESP_LOGV(SVK_TAG, "Starting loop task");
xTaskCreatePinnedToCore(
this->_loopImpl, // Function that should be called
"ESP32 SvelteKit Loop", // Name of the task (for debugging)
8*1024, // Stack size (bytes) 🌙 4096 to 6144 -> 8192
this, // Pass reference to this class instance
(tskIDLE_PRIORITY + 2), // task priority
&_loopTaskHandle, // Task handle
ESP32SVELTEKIT_RUNNING_CORE // Pin to application core
this->_loopImpl, // Function that should be called
"ESP32 SvelteKit Loop", // Name of the task (for debugging)
psramFound() ? 8 * 1024 : 6 * 1024, // Stack size (bytes) 🌙 4096 to 8192 / 6144
this, // Pass reference to this class instance
(tskIDLE_PRIORITY + 2), // task priority
&_loopTaskHandle, // Task handle
ESP32SVELTEKIT_RUNNING_CORE // Pin to application core
);
}

Expand Down Expand Up @@ -307,6 +307,5 @@ void ESP32SvelteKit::_loop()
#endif
}
vTaskDelayUntil(&xLastWakeTime, ESP32SVELTEKIT_LOOP_INTERVAL / portTICK_PERIOD_MS);
vTaskDelay(1); //🌙 just to be sure
}
}
4 changes: 0 additions & 4 deletions lib/framework/EventEndpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
#include <SecurityManager.h>
#include <StatefulService.h>

// heap-optimization: request heap optimization review
// on boards without PSRAM, heap is only 60 KB (30KB max alloc) available, need to find out how to increase the heap
// This class is used for each module, about 15 times, 112 bytes for each class (allocated in main.cpp, in global memory area) + each class allocates it's own heap

template <class T>
class EventEndpoint
{
Expand Down
33 changes: 27 additions & 6 deletions lib/framework/EventSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,32 +183,53 @@ void EventSocket::emitEvent(const String& event, const char *output, size_t len,
if (event != "monitor")
ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s", event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output);
#if FT_ENABLED(EVENT_USE_JSON)
client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
#else
client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
#endif
// 🌙 error check
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s to client %d: %s", event.c_str(), client->socket(), esp_err_to_name(result));
subscriptions.remove(originSubscriptionId);
}
}
}
else
{ // else send the message to all other clients

for (int subscription : client_subscriptions[event])
// 🌙 use iterator so remove also removes from the iterator
for (auto it = subscriptions.begin(); it != subscriptions.end(); )
{
int subscription = *it;
if (subscription == originSubscriptionId)
{
++it;
continue;
}
auto *client = _socket.getClient(subscription);
if (!client)
{
subscriptions.remove(subscription);
it = subscriptions.erase(it);
continue;
}
if (event != "monitor")
ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s", event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output);
#if FT_ENABLED(EVENT_USE_JSON)
client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
#else
client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
#endif
// 🌙 error check
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s to client %u: %s", event.c_str(), client->socket(), esp_err_to_name(result));
it = subscriptions.erase(it);
}
else
{
++it;
}
}
}

Expand Down
4 changes: 0 additions & 4 deletions lib/framework/HttpEndpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@

using namespace std::placeholders; // for `_1` etc

// heap-optimization: request heap optimization review
// on boards without PSRAM, heap is only 60 KB (30KB max alloc) available, need to find out how to increase the heap
// This class is used for each module, about 15 times, 152 bytes for each class (allocated in main.cpp, in global memory area) + each class allocates it's own heap

template <class T>
class HttpEndpoint
{
Expand Down
Loading