NetworkSendDriver
Source:
NetworkSendDriver.h
NetworkSendDriver¶
src/light/drivers/NetworkSendDriver.h:67Inherits:
DriverBase
Output driver: streams the buffer over UDP — one driver, three industry protocols selected by a control.
The single-node-multiple-protocols shape follows MoonLight's D_NetworkOut (architecture studied, not copied). Byte layouts live in [ArtNetPacket.h] / [E131Packet.h] / [DdpPacket.h], shared with the receiver so the two sides cannot drift.
Addressing: unicast to a LIST of receivers, and why that is the default.
One driver feeds N receivers (ips), each taking a contiguous run of the window (lightsPerIp, the same broadcasting idiom as an LED driver's ledsPerPin) and each addressed only by itself. So a wall of tubes is one driver, not N — the driver-level twin of one LED driver fanning out to N GPIO lanes.
The Art-Net 4 spec leaves no room here: "ArtDmx packets must be unicast to subscribers of the
specific universe contained in the ArtDmx packet… There are no conditions in which broadcast is
allowed." Broadcast survives only as legacy compatibility (type a broadcast address and it still works, SO_BROADCAST is set) — it is never the default.
The reason is receive cost, and it is asymmetric. An Art-Net universe number lives in the PAYLOAD, not in any header a switch or NIC can act on, so a receiver can only discard a universe it does not own AFTER its stack has carried the packet all the way up and parsed it. Broadcast therefore makes every host on the segment pay the receive cost of every universe, including hosts with nothing to do with lighting. Artistic Licence (the protocol's authors) say it plainly: "Broadcast data floods the entire network and appears at every node whether it needs it or not. Too much broadcast data overloads switches and nodes alike" — and Art-Net II added node discovery precisely so a controller could switch to unicast, which they credit with a "massive" reduction in network loading. Two independent sources put the practical broadcast ceiling at ~15 universes (Quasar Science; WLED issue #3297). A 128x128 grid is ~97 universes x 50 fps ~= 4,850 pkt/s (~20 Mbit/s) — measured on the bench starving an ESP32's network stack until its HTTP stopped answering. On WiFi it is worse: broadcast goes out at the lowest basic rate, unACKed, to every station, waking them all from power-save (RFC 9119).
What the choice is NOT about. "Unicast means sending the same data N times" only bites when several nodes need the SAME universe (mirroring). When each node owns a DIFFERENT slice — the normal case, and what this driver models — unicast duplicates nothing: the sender emits exactly as many packets as a broadcast stream would, and each reaches only its owner. So per-node unicast costs the sender the same and costs every other receiver nothing. Mirroring is the one case where a broadcast address is genuinely the better tool.
Liveness. UDP is fire-and-forget, so a dead receiver is invisible to the sender: the send loop simply tolerates it (a failed send drops that packet and moves on, so one dark tube cannot stall the others) rather than pretending to detect it. Real detection is ArtPoll/ArtPollReply discovery — the spec's own mechanism, and the next increment (backlog).
NOT multicast (no IGMP join yet — backlogged; it is sACN's native mode and the best answer on a switch with IGMP snooping, but degrades to a flood without it). E1.31 framing: CID stable per device (from the MAC), source name projectMM, priority 100, one frame-level sequence per frame.
Synchronous send: the whole frame goes out inline in [tick()] (~35 ms Ethernet / ~90 ms WiFi at 128×128 ArtNet; DDP less). A decoupling send task is a PSRAM-gated backlog item. Added per board via the catalog like the LED drivers; applies the same shared Correction, so network and wired outputs show identical colours.

Public Attributes¶
char ips = {}
char lightsPerIp = {}
: Lights per destination — the same idiom as an LED driver's ledsPerPin, and literally the same helper (assignCounts): blank = even split of the window, one number = that many each, a list = one per destination by position.
uint8_t protocol = 0
: Wire protocol (index into kProtocolOptions).
uint16_t universeStart = 0
: First universe the slice maps onto (ArtNet / E1.31; DDP is byte-addressed).
uint8_t fps = 50
: Send-rate ceiling (Hz); [tick()] rate-limits to this so a fast render tick doesn't flood the LAN.
Public Methods¶
inline NetworkSendDriver()
: DMX-over-network fixtures (ArtNet / E1.31 / DDP) are RGB by convention — the xLights/Falcon default — so a network sink references the "RGB" preset by default, unlike the WS2812 LED drivers that default to the strips' physical "GRB" order.
virtual inline void defineDriverControls() override
: This driver's own controls, added AFTER the per-driver correction block the base places at the top of every driver card: protocol, destination IP, universe offset, the shared window (start/count), then the rate cap.
virtual inline bool affectsPrepare(constchar * name) const override
: A start/count change resizes the window this sink sends, and a Custom channel-count change grows outChannels; both route through the prepare sweep so resizeCorrected() re-sizes corrected_ — otherwise growing past the old buffer silently drops to passthrough.
virtual inline void setup() override
: One-time wiring only: derive the stable E1.31 component id (CID) from the MAC once — no UUID machinery needed for a deterministic, unique-enough id.
virtual inline void release() override
: Close the socket on release, then chain to the base to clear any status this driver set.
virtual inline void setSourceBuffer(Buffer * buf) override
: Take the shared source buffer and (re)size the corrected_ buffer for it.
virtual inline void prepare() override
: Pure build (see MoonModule::prepare): open the UDP socket (idempotent), re-resolve the destination, and resize corrected_ off the hot path ([tick()] never allocates).
virtual inline void onCorrectionChanged() override
: A preset toggle (RGB↔RGBW) changes correction_.outChannels without a structural rebuild; rebuildCorrection() calls this hook so corrected_ tracks the new channel count.
virtual inline void tick() override
: Rate-limit to fps, apply this driver's correction into corrected_ (passthrough if it emits no channels), then chunk the window slice into protocol packets and send inline.
inline constBuffer & correctedBuffer() const
inline uint8_t destinationCount() const
: The destination table [prepare()] derived: how many receivers, each one's address, and how many lights of the window it owns.
inline constuint8_t * destinationAt(uint8_t i) const
inline nrOfLightsType lightsAt(uint8_t i) const
Public Static Attributes¶
constexprconstchar * kProtocolOptions = {"ArtNet", "E1.31", "DDP"}
: Protocol names, index-aligned with the constants used in [tick()]'s switch (0 = ArtNet, 1 = E1.31, 2 = DDP).
constexpruint8_t kProtocolCount = 3
constexpruint8_t kMaxDestinations = 32
: The receivers.