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
8 changes: 4 additions & 4 deletions docs/develop/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,16 @@ SvelteKit task

* Module::loop() runs in the SvelteKit task and calls getUpdate() to retrieve the updatedItem in a synchronized way, getUpdate() calls processUpdatedItem()
* processUpdatedItem() calls Module::onUpdate(), which is a virtual function which is overridden by Modules to implement custom functionality
* NodeManager::onUpdate() propagates onUpdate() to Node Controls (together with Node::updateControl()), guarded by nodeMutex
* NodeManager::onUpdate() propagates onUpdate() to Node Controls (together with Node::updateControl()), guarded by layerMutex

Driver Task

* PhysicalLayer::loopDrivers(): if requestMap call mapLayout(). mapLayout() calls onLayout(), guarded by nodeMutex
* PhysicalLayer::loopDrivers(): Node::onSizeChanged() and Node::loop() guarded by nodeMutex
* PhysicalLayer::loopDrivers(): if requestMap call mapLayout(). mapLayout() calls onLayout(), guarded by layerMutex
* PhysicalLayer::loopDrivers(): Node::onSizeChanged() and Node::loop() guarded by layerMutex

Effect Task

* PhysicalLayer::loop() calls VirtualLayer::Loop(): Node::onSizeChanged() and Node::loop(), guarded by nodeMutex
* PhysicalLayer::loop() calls VirtualLayer::Loop(): Node::onSizeChanged() and Node::loop(), guarded by layerMutex

## Core Assignments

Expand Down
35 changes: 35 additions & 0 deletions docs/develop/drivers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Drivers

## Parallel LED Driver

### Parlio (ESP32-P4)

### Parallel LED Driver Technical Implementation

For end-user documentation, see [Parallel LED Driver for ESP32-P4](#parallel-led-driver-for-esp32-p4).
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

Broken internal link fragment.

The anchor #parallel-led-driver-for-esp32-p4 doesn't exist in this document. Based on the docs/moonlight/drivers.md content, the correct link would be to that file's #parallel-led-driver section, or remove the link if it's meant to be internal.

Proposed fix
-For end-user documentation, see [Parallel LED Driver for ESP32-P4](#parallel-led-driver-for-esp32-p4).
+For end-user documentation, see [Parallel LED Driver](../../moonlight/drivers/#parallel-led-driver).
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

9-9: Link fragments should be valid

(MD051, link-fragments)

🤖 Prompt for AI Agents
In @docs/develop/drivers.md at line 9, The link fragment
'#parallel-led-driver-for-esp32-p4' is invalid; edit the line that reads "For
end-user documentation, see [Parallel LED Driver for
ESP32-P4](#parallel-led-driver-for-esp32-p4)" and either change the fragment to
the correct anchor '#parallel-led-driver' (which points to the Parallel LED
Driver section in the moonlight drivers doc) or remove the internal link
entirely if it was not intended to be an anchor link.


**Architecture Overview:**

The driver implements variable LEDs-per-pin support without intermediate buffers by padding during the transpose operation. Located in `src/MoonLight/Nodes/Drivers/parlio.cpp` with header `parlio.h`, called from `D_ParallelLEDDriver.h`.

**Key Functions:**

1. **`show_parlio()`** (parlio.cpp:409-414): Entry point receiving compact `channelsD` buffer and `leds_per_output[]` array. Computes two global state variables:
- `max_leds_per_output`: Maximum across all pins
- `first_index_per_output[16]`: Cumulative offsets for random access into compact buffer

2. **`create_transposed_led_output_optimized()`** (parlio.cpp:297): Main processing loop iterating `0..max_leds_per_output`. Applies color remapping (`offsetR/G/B/W`) and brightness LUTs before calling transpose.

3. **`transpose_32_slices()`** (parlio.cpp:181-183): **Core padding mechanism** using single conditional:

```cpp
const uint8_t data_byte = pixel_in_pin < pixels_per_pin[pin]
? brightness_cache[input_buffer[component_idx]] // Actual LED
: 0; // Padding (black)
```

When pixel_in_pin exceeds a pin's actual LED count, outputs zero, creating black padding pixels.

Data Flow: Input buffer remains compact (Σ(leds_per_pin[i]) × channels) → show_parlio() builds indexing structures → create_transposed_led_output_optimized() iterates to max → transpose_32_slices() conditionally reads/pads → DMA buffer written with uniform alignment → PARLIO hardware transmits.

Performance: Zero memory overhead for padding, O(1) offset lookup via first_index_per_output[], minimal branching in hot path. Chunking logic (parlio.cpp:480-505) handles large LED counts exceeding DMA limits.
34 changes: 28 additions & 6 deletions docs/moonlight/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,14 @@ Want to add a Driver to MoonLight, see [develop](../../develop/overview/). See a

| Name | Preview | Controls | Remarks
| ---- | ----- | ---- | ---- |
| Parallel LED Driver | <img width="100" src="https://github.com/user-attachments/assets/9cbe487e-f330-40a5-8b40-6663c83e5d90"/> | <img width="320" alt="Parallel" src="https://github.com/user-attachments/assets/0c6f1543-623a-45bf-98d7-f5ddd072a1c6" /> | Drive multiple LED types, all devices including ESP32-P4(-nano) supported<br>Light preset: See below<br>DMA buffer: set higher when LEDs flicker<br>Virtual LED Driver will be part of the Parallel LED driver.|
| Parallel LED Driver | <img width="100" src="https://github.com/user-attachments/assets/9cbe487e-f330-40a5-8b40-6663c83e5d90"/> | <img width="320" alt="Parallel" src="https://github.com/user-attachments/assets/0c6f1543-623a-45bf-98d7-f5ddd072a1c6" /> | Drive multiple LED types, all devices including ESP32-P4(-nano) supported<br>Light preset: See below<br>DMA buffer: set higher when LEDs flicker<br>See [below](#parallel-led-driver) |
| FastLED Driver | <img width="100" src="https://avatars.githubusercontent.com/u/5899270?s=48&v=4"/> | <img width="320" alt="FastLed" src="https://github.com/user-attachments/assets/d5ea1510-9766-4687-895a-b68c82575b8f" /> | Most used LED driver. Drive most common LEDs (WS2812). |
| Art-Net Out| <img width="100" src="https://github.com/user-attachments/assets/9c65921c-64e9-4558-b6ef-aed2a163fd88"> | <img width="320" alt="Art-Net" src="../../media/moonlight/drivers/ArtNetOutControls.png" /> | Send Art-Net to Drive LEDS and DMX lights over the network. See [below](#art-net-out) |
| Art-Net In | <img width="100" src="../../media/moonlight/drivers/Art-Net-In.png"> | <img width="320" alt="Art-Net" src="../../media/moonlight/drivers/ArtNetInControls.png" /> | Receive Art-Net (or DDP) packages e.g. from [Resolume](https://resolume.com/) or Touch Designer. See [below](#art-net-in) |
| Audio Sync | <img width="100" src="https://github.com/user-attachments/assets/bfedf80b-6596-41e7-a563-ba7dd58cc476"/> | No controls | Listens to audio sent over the local network by WLED-AC or WLED-MM and allows audio reactive effects (♪ & ♫) to use audio data (volume and bands (FFT)) |
| HUB75 Driver | <img width="100" src="https://github.com/user-attachments/assets/620f7c41-8078-4024-b2a0-39a7424f9678"/> | <img width="100" src="https://github.com/user-attachments/assets/4d386045-9526-4a5a-aa31-638058b31f32"/> | Drive HUB75 panels<br>Not implemented yet |
| IR Driver | <img width="100" src="../../media/moonlight/drivers/IRDriver.jpeg"/> | <img width="100" src="../../media/moonlight/drivers/irdrivercontrols.png"/> | Receive IR commands and [Lights Control](../../moonlight/lightscontrol/) |

* The Parallel LED driver uses different hardware peripherals depending on the MCU type: ESP32-D0: I2S, ESP32-S3: LCD_CAM, ESP32-P4: Parallel IO (ParLIO).
* Virtual LED Driver: Driving max 120! outputs (E.g. 48 panels of 256 LEDs each run at 50-100 FPS) using shift registers. Integrated within the Parallel LED Driver architecture. Not implemented yet
<img width="100" src="https://github.com/user-attachments/assets/98fb5010-7192-44db-a5c9-09602681ee15"/><img width="100" src="https://github.com/user-attachments/assets/c81d2f56-00d1-4424-a716-8e3c30e76636"/>

### Light Preset

* **Max Power**: moved to [IO Module](../../moonbase/inputoutput) board presets.
Expand All @@ -68,13 +64,39 @@ Want to add a Driver to MoonLight, see [develop](../../develop/overview/). See a
!!! info "Custom setup"
These are predefined presets. In a future release custom presets will be possible.

### Parallel LED Driver

<img width="320" alt="Parallel" src="https://github.com/user-attachments/assets/0c6f1543-623a-45bf-98d7-f5ddd072a1c6" />

* The Parallel LED driver uses different hardware peripherals depending on the MCU type: ESP32-D0: I2S, ESP32-S3: LCD_CAM, ESP32-P4: Parallel IO (ParLIO).
* For ESP32-D0 and ESP32-S3 the [I2S clockless driver](https://github.com/hpwit/I2SClocklessLedDriver) is used
* For ESP32-P4 the [parlio driver](https://github.com/troyhacks/WLED) is used.
* Virtual LED Driver will be part of the Parallel LED driver: Driving max 120! outputs (E.g. 48 panels of 256 LEDs each run at 50-100 FPS) using shift registers. Integrated within the Parallel LED Driver architecture. Not implemented yet

<img width="100" src="https://github.com/user-attachments/assets/98fb5010-7192-44db-a5c9-09602681ee15"/><img width="100" src="https://github.com/user-attachments/assets/c81d2f56-00d1-4424-a716-8e3c30e76636"/>

**Parlio (ESP32-P4)**

*Created by @TroyHacks, extended by @ewowi*

The ESP32-P4 Parallel LED Driver uses the hardware PARLIO peripheral to control up to **16 LED strips simultaneously** with independent pixel counts per strip. This enables high-performance setups with thousands of LEDs while maintaining accurate timing through DMA-based transmission.

**Key Features:**

- **Variable LEDs per strip**: Each GPIO pin can drive a different number of WS2812/SK6812 LEDs (e.g., Pin 0: 100 LEDs, Pin 1: 50 LEDs, Pin 2: 120 LEDs)
- **Automatic padding**: Shorter strips receive black pixels to maintain timing alignment—no visual impact
- **Memory efficient**: Only stores actual LED data, padding happens during hardware transmission
- **High-speed operation**: Supports 800 kHz to 1.2 MHz clock speeds with auto-overclocking for smaller LED counts
- **RGB/RGBW support**: Configurable color ordering and per-component brightness correction
- **Configuration**: Assign GPIO pins in the MoonLight interface and specify LED counts per pin. The driver automatically calculates the maximum LEDs per pin and handles synchronization.

### Art-Net Out ☸️

<img width="300" src="../../media/moonlight/drivers/ArtNetOutControls.png"/>

Sends Lights in Art-Net compatible packages to an Art-Net controller specified by the IP address(es) provided.

#### Controls
**Controls**

* **Light preset**: See above.
* **Controller IPs**: The last segment of the IP address within your local network, of the hardware Art-Net controller. Add more IPs if you send to more than one controller, comma separated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import Help from '~icons/tabler/help';
import FieldRenderer from '$lib/components/moonbase/FieldRenderer.svelte';

let filesState: any = $state({});;
let filesState: any = $state({});
let folderList: FilesState[] = $state([]); //all files in a folder
let editableFile: FilesState = $state({
name: '',
Expand Down
24 changes: 11 additions & 13 deletions interface/src/routes/moonbase/monitor/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export let colors: number[] = [];
// Store LED matrix dimensions
let matrixWidth: number = 1;
let matrixHeight: number = 1;
let matrixDepth: number = 1;

let colorBuffer: WebGLBuffer; // Buffer for color data

Expand Down Expand Up @@ -116,10 +117,10 @@ export function clearVertices() {
vertices = [];
}

// New function to set the LED matrix dimensions
export function setMatrixDimensions(width: number, height: number) {
export function setMatrixDimensions(width: number, height: number, depth: number = 1) {
matrixWidth = width;
matrixHeight = height;
matrixDepth = depth;
}

export const updateScene = (vertices: number[], colors: number[]) => {
Expand Down Expand Up @@ -155,34 +156,31 @@ function getMVPMatrix(): mat4 {
const projection = mat4.create();
mat4.perspective(projection, fov, canvasAspect, near, far);

// Normalize the matrix dimensions to a unit square/rectangle
// Use the larger dimension as the base
const maxDim = Math.max(matrixWidth, matrixHeight);
// Normalize dimensions
const maxDim = Math.max(matrixWidth, matrixHeight, matrixDepth);
const normalizedWidth = matrixWidth / maxDim;
const normalizedHeight = matrixHeight / maxDim;

// Calculate the required camera distance to fit the matrix in view
// Determine which dimension is limiting based on canvas aspect
const verticalSize = normalizedHeight;
const horizontalSize = normalizedWidth;
const normalizedDepth = matrixDepth / maxDim;

// Calculate required distance for vertical fit
const verticalSize = normalizedHeight;
const distanceForHeight = verticalSize / (2 * Math.tan(fov / 2));

// Calculate required distance for horizontal fit
const horizontalFov = 2 * Math.atan(Math.tan(fov / 2) * canvasAspect);
const horizontalSize = normalizedWidth;
const distanceForWidth = horizontalSize / (2 * Math.tan(horizontalFov / 2));

// Use the larger distance to ensure both dimensions fit
const cameraDistance = Math.max(distanceForHeight, distanceForWidth) * 2.5; // 1.2 adds some padding
const cameraDistance = Math.max(distanceForHeight, distanceForWidth) * 2.5;

const view = mat4.create();
mat4.lookAt(view, [0, 0, cameraDistance], [0, 0, 0], [0, 1, 0]);

const model = mat4.create();

// Scale by the normalized dimensions to get correct proportions
mat4.scale(model, model, [normalizedWidth, normalizedHeight, 1]);
// Scale by ALL normalized dimensions
mat4.scale(model, model, [normalizedWidth, normalizedHeight, normalizedDepth]);

const mvp = mat4.create();
mat4.multiply(mvp, projection, view);
Expand Down
Loading