Skip to content

DriverLayer

Blends all registered EffectsLayers into its own pixel buffer each loop(). Drivers and preview modules read from this layer. Renamed from LightsConsumer in 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

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.