DriverLayer¶
Blends all registered EffectsLayers into its own pixel buffer each loop(). Drivers and preview modules read from this layer. Renamed from
LightsConsumerin Release 3, Sprint 3b as part of the MoonLight-vocabulary alignment — the name now reflects the role (a driver layer that holds layouts and drivers) rather than the data direction.
What it does¶
DriverLayer is a StatefulModule that owns one output Channel. On each loop() call it reads the latest frame from every registered EffectsLayer and blends them into its buffer using pixel-wise saturating ADD (clamp to 255). The result is published via an atomic pointer, making it safe for a consumer on a separate thread or core.
If no source has published a frame yet, pixels default to black (0, 0, 0).
Props and wiring¶
| Parameter | Source | Default | Description |
|---|---|---|---|
width |
setProps |
10 | Buffer width in pixels |
height |
setProps |
10 | Buffer height in pixels |
depth |
setProps |
10 | Buffer depth in pixels (1 = flat 2D) |
source |
setInput("source", ...) |
— | Registers an EffectsLayer* as a blend source |
Multiple sources can be registered by providing multiple "source" entries. Up to kMaxDriverSources (8) sources are supported.
Direct wiring (tests / manual use)¶
void addSource(EffectsLayer* src);
Called before setup(). On teardown() the source list is cleared, so addSource() must be called again before the next setup().
Consumer interface¶
Channel* readyChannel() const; // atomic read — safe to call from any thread/core
Returns the latest blended frame, or nullptr before the first loop().
Binary snapshot¶
bool snapshot(std::vector<uint8_t>& buf) const override;
Format: [0x02][w lo][w hi][h lo][h hi][d lo][d hi][R G B …]
Used by the WebSocket pixel preview endpoint.
Lifecycle¶
| Phase | Action |
|---|---|
setProps() |
Sets width, height, depth |
setInput() |
Calls addSource() for each "source" input entry |
setup() |
Allocates the output pixel buffer |
loop() |
Blends all sources into the buffer (saturating ADD) and publishes |
teardown() |
Frees the pixel buffer; clears readyChannel() to nullptr; resets source list |
Health report¶
healthReport() returns buffer_kb=N psram=1/0 sources=N WxHxD.
buffer_kb: allocated output buffer size (KB, rounded down).psram: 1 when the buffer is in PSRAM, 0 when in internal RAM.sources: number of registered EffectsLayer sources.WxHxD: current pixel dimensions.
Visible via GET /api/system and in the 10 s health-check serial log.
Core affinity¶
preferredCore() returns 1 (App core, the default). On ESP32, DriverLayer and its children (GridLayout, PreviewModule) all run on Core 1 after the binary semaphore signals that all EffectsLayers on Core 0 have published their frames. This keeps the output coordination pipeline on one core and frees Core 0 for effects computation.
On PC all modules run sequentially — core affinity has no effect.
Platform notes¶
- PSRAM allocation on ESP32-S3 via
pal::psram_malloc()when PSRAM is present;malloc()otherwise. - Single output buffer (no double buffer). Safe when Core 1 only reads after the binary semaphore is given.
- The blend loop is O(pixels x sources) per tick. For 10x10x10 pixels and 2 sources: 2 000 iterations, fast on any platform.
Source¶
- src/modules/layers/DriverLayer.h
- src/modules/layers/EffectsLayer.h
- tests/test_layers.cpp
- tests/test_module_manager.cpp
Test coverage¶
→ Effect and Driver Layers — zero-source black output, single-producer pass-through, two-producer saturating blend, GridLayout child override, OOB blend guard.
→ Brightness Modifier — multi-producer wiring (source + source2).
Live pipeline: test2 — DriverLayer blending two EffectsLayers simultaneously on real hardware.