Skip to content

Latest commit

 

History

History
188 lines (114 loc) · 17.8 KB

File metadata and controls

188 lines (114 loc) · 17.8 KB

Drivers

A driver sends lights somewhere. It reads its slice of the Drivers container's shared buffer, applies its own output correction, and outputs — over a wire (WS2812), the network (Art-Net / E1.31 / DDP), to a smart-light hub (Hue), or to the web UI (Preview).

Several drivers can share one buffer, each driving its own slice. Every driver starts with the same shared controls, then adds its own. Drivers are added per board through the catalog (deviceModels.json); PreviewDriver is the one boot-wired driver.

Jump to: shared controls · LED · Network · Smart light · Preview

Shared driver controls

Shared 💫 · every driver

Added once by DriverBase so no driver re-implements it: a per-driver output correction (how this driver's slice looks) and a source window (which slice of the shared buffer it reads). Every driver card leads with this block; its own controls follow.

Shared driver controls: localBrightness, lightPreset, whiteMode, start, count

  • localBrightness — this driver's dim (0–255), multiplied with the global brightness into one LUT; both sliders reach the output.
  • lightPreset — the light preset this driver applies per light (channel order / RGBW synthesis). At runtime the driver holds the preset's stable id, so reordering presets never disturbs the reference; the reference survives a reboot because the preset's name is persisted and re-resolved on load. The one caveat is renaming: within a session the id keeps the link, but after a reboot a renamed preset no longer matches the persisted name, so the driver falls back to the default preset — re-pick it if you rename a preset a driver uses.
  • whiteMode — how the white channel is derived for an RGBW strip, applied only when the referenced preset carries a W channel.
  • start — first light of the shared buffer this driver reads (default 0).
  • count — how many lights from start this driver drives. Blank / default drives all lights; set a number to output only that slice — the way multiple drivers each own a section of one buffer (an onboard status LED at 0, the main strip from 1).

Detail: technical

LED drivers

LED driver 💫 · wire

Addressable WS2812B-class LEDs over a wire, same controls and same wire contract however the bits reach the pins. Two drivers: RMT for a few strands, and ParallelLedDriver for many (up to 16) clocked out at once. The parallel driver has a peripheral control that picks the DMA peripheral, offering only the ones the chip supports:

  • i80 — the esp_lcd i80 bus (LCD_CAM on any chip that has it — the S3, P4, and S31 — the I2S peripheral on the classic ESP32). The general default for many strands.
  • Parlio — the Parallel-IO peripheral (P4 and S31).
  • MoonI80 — our own GDMA below esp_lcd (LCD_CAM chips only — S3, P4, S31): a streaming ring for more lights than one DMA buffer holds, plus a 74HCT595 pin expander that turns 6 pins into 48 strands.

Which to pick, and why: details.

LED output driver controls

Plus the shared controls above:

The card reads top-down as invariant controls → peripheral divider → peripheral-specific controls:

  • pins — data GPIO list, e.g. 18,17,16, or inclusive ranges like 20-23 (= 20,21,22,23) mixed freely (20-22,35,38-40). One strand each — or, with the MoonI80 pin expander, one group of 8. Empty idles until set; changing it re-inits live.
  • ledsPerPin — lights per strand, following the broadcasting idiom (cf. NumPy / CSS shorthand): empty = even split of the window; one number = that many on every strand (64 → 64 each); a list 3,4,5 = one per strand by position (a short list even-splits the remainder). Shorter strands go dark early while the longest finishes. Through an expander an entry is one strand, not one pin, so two strands on one '595 can differ.
  • peripheral (the divider) — the DMA peripheral driving the bus (i80 / Parlio / MoonI80), filtered to what the chip supports. Everything above it is invariant (which LEDs and how many); everything below is what the chosen peripheral supports. Switching it re-surfaces that peripheral's own controls and re-inits live. Always shown — with a single option it reads as a labeled indicator of what's driving the LEDs.
  • peripheral-specific (below the divider) — each shown only on the peripherals that support it, so the set changes when you switch peripheral:
    • doubleBuffer — the async second frame buffer (encode overlaps the wire). Shown on i80 and Parlio (they route through a real transaction queue); hidden on MoonI80, which runs single-buffer (its speed comes from the streaming ring, not from double-buffering a whole frame).
    • pinExpander — the 74HCT595 fan-out (one pin → 8 strands). Shown on the LCD_CAM family (i80 and MoonI80), hidden on Parlio (its single-shot transfer can't carry the ×8 fan-out frame).
    • i80: the WR/DC bus pins (clockPin/dcPin). MoonI80: shiftOverclock and the ring* geometry cluster. Parlio: no extra pins.
  • Expert-only (🔧, shown when System.expertMode is on): loopbackTest — a TX→RX loopback self-test (jumper the first pin to loopbackRxPin), verdict in the status field, with loopbackTxPin/loopbackRxPin its wiring.

Two ParallelLedDriver instances that select peripherals on the same hardware block (e.g. both i80 and MoonI80, which share LCD_CAM) conflict — the second idles with a status. Different blocks (RMT + Parlio + i80 on a P4) coexist.

Origin: WS2812B on FastLED / hpwit / WLED prior art (analysis)

Tests: RMT · shared + peripherals

Detail: RMT · Parallel · peripherals: i80 · MoonI80 · Parlio

Network drivers

Network Send 💫 · UDP

NetworkSend controls

Streams the buffer over UDP as Art-Net, E1.31 / sACN, or DDP — one burst per frame, compatible with Falcon/Advatek controllers, xLights, and LedFx. Feeds one or more receivers from a single driver: each gets its own slice of the window, unicast to its own address.

  • protocol — Art-Net / E1.31 / DDP (default Art-Net); the destination port follows automatically.
  • ips — the receivers. Blank by default — the driver idles until set, so it never sends uninvited traffic. Type the full address once, then a range or a list: 192.168.1.70-74 (five tubes, ends inclusive) or 192.168.1.60,61,62,65; both mix, and a further full address switches subnet.
  • lightsPerIp — lights per receiver, same idiom as an LED driver's ledsPerPin: blank = split the window evenly; one number = that many each; a list 150,100,50 = one per receiver by position.
  • universe_start — first universe for Art-Net / E1.31 (DDP ignores it). Restarts per receiver — each is an independent node addressing its own strip.
  • fps — frame-rate limit (default 50, 1–120).

Unicast is the default because Art-Net 4 requires it and because broadcast makes every host on the LAN parse every packet; a broadcast address still works if you type one. The full addressing rationale (and the one case where broadcast is the better tool) is on the detail page.

Origin: MoonLight D_NetworkOut; Art-Net 4 / E1.31 / DDP specs

Tests

Detail: technical

Panel Card 💫 · raw Ethernet

PanelCard controls

Streams the buffer to LED panel cards as raw Ethernet frames, compatible with ColorLight 5A-75 cards. In vendor terms (ColorLight, NovaStar, Linsn) these are receiving cards, and this driver takes the place of the sending card that normally feeds them. These take a sender-card feed rather than a pixel protocol, so the driver sends row-addressed data followed by a sync frame that latches the image.

The board renders and sends: effects, layers and MoonLive run on the device, so one board replaces a host PC driving the same panels. Add a Network Receive effect to take Art-Net in as well.

  • format — the card's wire format (ColorLight 5A-75).
  • No geometry controls — the wall comes from the Layout. A PanelsLayout already states how many panels there are, their size, wiring order and snaking; this driver reads the finished picture and cuts it into card rows. A row wider than 497 pixels goes out as several packets.
  • interface — which NIC to send from on desktop/Raspberry Pi (eth0, en0). Ignored on ESP32, which has one MAC. Raw sending needs root; without it the driver records frames instead and says so.
  • fps — frame-rate limit (default 40, 1–120).

These cards need a 1 Gbit link. Not for bandwidth — a 256×256 panel at 40 fps is only ~65 Mbit/s — but for wire time: the cards have no buffering and latch on the sync frame, so a whole frame must arrive inside the inter-frame window. At 100 Mbit the same bytes take ten times as long, which breaks that timing and shows up as tearing or wrong rows rather than as an error. The driver reads the negotiated speed and warns, but still sends: a small panel may be fine, and a measurement beats a refusal.

No IP is involved — no address, no port, no DHCP — so the driver works on a link that never got a lease.

Origin: ColorLight 5A-75 documented byte layout

Tests

Detail: technical

Smart light drivers

Hue 💫 · bridge

A HueDriver in the UI

Drives Philips Hue bulbs as pixels: each color bulb in the driver's window becomes one pixel, pushed to the bridge over its HTTP API. Paced to the bridge's ~10 cmd/s limit — smooth ambient color, not strobing.

  • bridgeIp — the bridge's LAN IPv4.
  • appKey — the Hue app key; filled by pair, persisted.
  • pair — button: press it, then the bridge's physical link button within ~30 s to claim a key.
  • room / light — dropdowns narrowing which color lights are driven (both default All).

Origin: projectMM, on the Hue v1 CLIP API

Tests

Detail: technical

Preview drivers

Preview 💫 · web UI

PreviewDriver controls

Streams a true-shape 3D preview to the web UI over WebSocket as a point list — only the real lights at their real positions, so a sphere/ring/arbitrary map shows in its true shape. The one boot-wired driver.

  • fps — preview stream rate (default 24, 1–60; independent of the render loop).

Origin: projectMM, on MoonLight's PhysicalLayer model

Tests

Detail: technical

LED driver — details

Which driver?

RMT is its own driver; the rest are peripheral choices on the one Parallel LED driver.

Want Use Why
A few strands, any ESP32 RMT driver The default. Simple, no bus width to think about, one channel per strand.
Many strands (up to 16) Parallel LED, peripheral i80 The scale path where RMT runs out of channels. One DMA transfer drives every strand at once.
Up to 16 strands on a P4 Parallel LED, peripheral Parlio The P4's own parallel peripheral — it generates its pixel clock, so there is no clock pin to spend.
More lights than fit one DMA buffer, or more strands than you have GPIOs Parallel LED, peripheral MoonI80 The same LCD_CAM output as i80, on our own DMA: it streams the frame, and it drives a 74HCT595 pin expander — 6 pins → 48 strands.

MoonI80 and i80 drive the same pins the same way; only the DMA underneath differs. Start with i80 — it is the proven path. Choose MoonI80 when you hit one of its two limits. Both are peripheral choices on the one Parallel LED driver, so you switch between them with the peripheral control on one board with no reflash.

RMT vs the three parallel peripherals. All drive WS2812B-class strips with the same pins / ledsPerPin / loopback* controls and the same wire contract; they differ in parallelism, chip, and — for the two i80-bus peripherals (i80 and MoonI80) — in who programs the DMA.

Lane, pin, strand. A lane is one bus data line; a strand is one chain of LEDs. The i80 bus is 8 or 16 lanes wide (a hardware fact — lcd_ll_set_data_wire_width takes nothing else), but you configure only the pins that drive something, at any count from 1: the driver rounds the bus up around them and parks the spare lanes on a pin the peripheral already drives, where nothing reads them.

  • Direct: one pin = one lane = one strand. 1–16 strands.
  • Through an expander: each data pin feeds one '595 and fans out to 8 strands, so 1–8 data pins → up to 64 strands (the driver's ceiling). The latch also costs a lane — the peripheral has only one clock output, so it has to ride a data line — but the strand ceiling binds first. hpwit's board populates 6 pins → 48 strands.
Output peripheral Chip Strands Extra controls Notes
RMT (detail) (own driver) any ESP32 (classic 8 ch, S3 4, P4 4 DMA) one per RMT TX channel loopbackFrame The general single-/few-strand output; default for classic + S3 board entries. loopbackFrame bit-verifies a whole frame, catching frame-rate / RF corruption a 24-bit burst misses.
Parallel LED i80 S3 / P4 / S31 (LCD_CAM) · classic (I2S) 1–16 clockPin dcPin Over IDF's esp_lcd i80 bus. The bus is 8 or 16 bits wide (≤8 pins → 8-bit, 9–16 → 16-bit) — but the pin count is free: configure only the pins that drive something and the driver rounds the bus up around them, parking the spare lanes on a pin the peripheral already drives. clockPin/dcPin are i80 bus lines the LEDs ignore. Capped by one contiguous DMA buffer: the classic backend is internal-RAM only (I2S can't reach PSRAM) → 2048 lights; LCD_CAM draws from PSRAM → 16384. Over the cap it idles with a status rather than crashing.
Parallel LED MoonI80 S3 / P4 / S31 (LCD_CAM only) 1–16; ×8 per pin with an expander (6 pins → 48 strands) clockPin pinExpander latchPin useRing ringAuto; 🔧 shiftOverclock ringRows ringBufs ringPadUs The same LCD_CAM output as i80 on our own GDMA chain, which buys two things esp_lcd cannot: a frame streamed through a small buffer pool instead of held whole (so length stops being a memory question), and a 74HCT595 pin expander — one GPIO fans out to 8 strands. ringAuto (default on) derives the streaming geometry per config, so the manual ring* knobs and shiftOverclock (a faster '595 clock for short-wired rigs) are expert-only tuning — the full guide is on the technical page. No dcPin at all, and WR is routed only when a '595 needs it as its shift clock. Not on the classic ESP32 (its i80 is the I2S peripheral). The prime-only ring (frame fits the buffer pool) and the pin expander are wall-solid; the lapping ring (very long strands, where the ISR refills from a PSRAM source) has a known last-row sparkle on the largest configs, tracked in the backlog. Why + what it costs: ADR-0014.
Parallel LED Parlio ESP32-P4 1–16 The P4's parallel path; Parlio generates its own pixel clock, so no clock/dc pins to spend. Bus width follows the pin count. On P4-NANO a known-good 8-set is 20,21,22,23,24,25,26,27.

The Parallel LED technical page carries the wire contract, buffer slicing, memory sizing, and the loopback self-test; each peripheral's own page (i80 · MoonI80 · Parlio) covers its DMA specifics.

What the peripherals share. The three parallel peripherals are thin shells the one Parallel LED driver selects between; two common pieces do the real work — worth reading if you care how a frame is actually built:

  • Parallel LED driver — the shared body: strand slicing, the encode loop, the async double-buffer, the latch pad, the loopback self-test. A peripheral backend adds only its own DMA calls.
  • Slot encoder — the wire format itself. Each WS2812 bit becomes three bus slots (pulse start / data / tail), and the data slot is an 8×8 bit transpose: lanes in, bit-planes out, so one bus word carries the same bit of every strand. It is the render loop's measured hot spot.