node-dmx is a TypeScript library for controlling DMX over IP from Node.js.
It supports:
rdmnet/packet), Broker PDU (rdmnet/broker), and RPT PDU (rdmnet/rpt) are wire-format-correct per ANSI E1.33, verified byte-for-byte against ETCLabs' open-source E1.33 reference implementation (root-layer extended flags+length, Broker Connect/Connect Reply/Client Entry/Client List/Disconnect/Null, RPT header + Request/Notification/Status PDUs including the RDM Command PDU's leading-0xCC encoding).rdmnet/llrp-transport: LlrpManager, LlrpTarget) implements the real ANSI E1.33 ยง6 UDP multicast transport โ two multicast groups (request/response), the 27-byte LLRP PDU header, Probe Request/Reply, RDM command passthrough, and the reference discovery algorithm (ranged probing with a known-UID exclusion list, gated by consecutive clean rounds). It is intentionally not carried over RdmnetClient's TCP connection, matching the spec's requirement that LLRP work without a live Broker session.| Area | Feature | sACN (E1.31) | Art-Net 4 | RDMnet (E1.33) | Notes |
|---|---|---|---|---|---|
| Core I/O | DMX Send | โ | โ | โ | DMXController sends DMX via sACN/Art-Net |
| Core I/O | DMX Receive / Listen | โ | โ | โ | Receiver, ArtNetReceiver โ RDMnet carries RDM device management, not DMX slot data |
| Core I/O | Synchronization / Discovery | โ | โ (ArtSync) | n/a | Sender.sendSync, UniverseDiscoverySource (E1.31 ยง6/ยง8) |
| Device Discovery | Controller/Node Discovery | โ ๏ธ Discovery packets | โ | โ | ArtNetDiscovery, RdmnetDiscovery (DNS-SD + mDNS), LlrpManager |
| Device Management | RDM Messaging (controller) | โ | โ | โ | ArtNetRdmClient full flow, RdmnetClient RPT rdmTransaction, LlrpManager RDM passthrough |
| Device Management | RDM Responder (fixture/device) | n/a | โ | โ | RdmResponder core + ArtNetRdmResponder / RdmnetClient.useRdmResponder / LlrpTarget |
| Security | Secure Transport | โ | โ | โ | RdmnetClient supports transport: 'tls', peer auth policy, post-connect auth hook |
| Ecosystem | Fixture Plugin System | โ | โ | โ | Fixture API currently targets DMX output protocols |
| Compliance | Formal Protocol Compliance Claim | โ E1.31 implementation | โ Art-Net implementation | โ ๏ธ Wire-format verified | RDMnet: root/Broker/RPT/LLRP wire-verified; full claim depends on third-party conformance runs |
This guide is written for students and first-time users: start simple, then move to advanced features.
npm install node-dmx
flush() to send them.import {DMXController} from 'node-dmx';
const controller = new DMXController({protocol: 'sacn'});
const universe = controller.addUniverse(1);
universe.setChannel(1, 255); // channel 1 at full
await controller.flush();
controller.close();
import {DMXController} from 'node-dmx';
const controller = new DMXController({
protocol: 'artnet',
artSync: true,
artnet: {
host: '255.255.255.255',
broadcast: true,
},
});
controller.setChannel(1, 1, 255); // universe 1, channel 1, full
await controller.flush();
controller.close();
import {DMXController} from 'node-dmx';
const controller = new DMXController({protocol: 'sacn'});
const u1 = controller.addUniverse(1);
const u2 = controller.addUniverse(2);
u1.setChannel(1, 255);
u2.setChannel(1, 128);
await controller.flush(); // sends all dirty universes
import {ArtNetDiscovery} from 'node-dmx';
const discovery = new ArtNetDiscovery();
const replies = await discovery.pollOnce();
for (const node of replies) {
console.log(node.ip, node.shortName, node.longName);
}
discovery.close();
import {ArtNetRdmClient, RdmCommandClass, PIDS} from 'node-dmx';
const client = new ArtNetRdmClient({host: '192.168.0.10'});
const response = await client.rdmTransaction(1, {
destinationUid: {manufacturerId: 0x7a70, deviceId: 0x00000001},
sourceUid: {manufacturerId: 0x7a70, deviceId: 0x00000002},
transactionNumber: 1,
portId: 1,
subDevice: 0,
commandClass: RdmCommandClass.GET_COMMAND,
pid: PIDS.DEVICE_INFO,
});
console.log(response);
client.close();
RdmResponder is a transport-agnostic RDM device core โ it answers GET/SET for the
standard PID set (DEVICE_INFO, DMX_START_ADDRESS, IDENTIFY_DEVICE, DEVICE_LABEL,
DMX_PERSONALITY, MANUFACTURER_LABEL, SUPPORTED_PARAMETERS, DISC_MUTE/UN_MUTE...) for you โ
no manual ACK/NACK frame or checksum handling. Three wrappers plug it into each transport:
import {ArtNetRdmResponder, RdmResponder} from 'node-dmx';
const uid = {manufacturerId: 0x7a70, deviceId: 0x00000042};
const responder = new RdmResponder({
uid,
deviceLabel: 'Sim Fixture 1',
dmxStartAddress: 1,
personalities: [{footprint: 3, description: 'RGB'}],
onIdentifyChange: (on) => console.log('identify', on),
});
// Art-Net: answers ArtPoll/ArtTodRequest/ArtRdm; discoverable via ArtNetDiscovery.
const fixture = new ArtNetRdmResponder({responder, universe: 1});
fixture.on('dmx', (frame) => console.log('levels', frame.subarray(0, 3)));
ArtNetRdmResponder (also receives ArtDMX for the configured universe).RdmnetClient.useRdmResponder(responder) โ auto-answers inbound RPT Requests.new LlrpTarget({..., onRdmCommand: (frame) => responder.respond(frame)}).See examples/artnet-fixture-simulator.ts, examples/rdmnet-fixture-simulator.ts, and
examples/rdmnet-llrp-fixture-simulator.ts.
import {DMXController, Fixture, RGBDimmerFixture} from 'node-dmx';
const controller = new DMXController({protocol: 'sacn'});
controller.addUniverse(1);
const fixture = new Fixture(
controller,
RGBDimmerFixture,
1, // universe
1, // start address
'4ch', // personality id
);
// RGBDimmerFixture expects state with `dimmer` and `rgb` array values in range 0..1
fixture.set({dimmer: 1, rgb: [1, 0.25, 0.1]});
fixture.render();
await controller.flush();
Available built-in example fixture plugins:
RGBDimmerFixture (4ch: dimmer + RGB)RGBWW5Fixture (5ch: R + G + B + warm white + cool white)All examples live in examples/.
Core examples:
examples/sacn-basic.tsexamples/sacn-animate.tsexamples/sacn-animate-orignal.ts (older single-universe variant)examples/sacn-rainbow.tsexamples/sacn-sync.ts โ Synchronization Packets (E1.31 ยง6)examples/sacn-universe-discovery.ts โ Universe Discovery (E1.31 ยง8)examples/sacn-listen-matrix.tsexamples/artnet-animate.tsexamples/artnet-listen-matrix.tsexamples/artnet-discover.tsexamples/artnet-fixture-simulator.ts โ simulate a DMX+RDM fixtureexamples/rdm-tod.tsexamples/rdmnet-listen.tsexamples/rdmnet-discover.tsexamples/rdmnet-interop-smoke.tsexamples/rdmnet-rdm-transaction.ts โ RDM GET over RPTexamples/rdmnet-llrp-discover.ts โ real LLRP UDP device discoveryexamples/rdmnet-fixture-simulator.ts โ simulate an RDMnet RDM deviceexamples/rdmnet-llrp-fixture-simulator.ts โ simulate an LLRP Targetexamples/fixture-rgbww5.tsexamples/list-network-interfaces.tsAdditional animation examples:
examples/sacn-comet-chase.tsexamples/sacn-fire-flicker.tsexamples/sacn-police-strobe.tsexamples/sacn-breathing-pastels.tsexamples/sacn-color-wipe.tsexamples/sacn-sparkle.tsexamples/sacn-theater-chase.tsexamples/sacn-aurora.tsexamples/sacn-pulse-stack.tsexamples/sacn-storm.tsexamples/sacn-halloween.tsexamples/sacn-christmas.tsRun an example:
npm run example/sacn-rainbow
All sacn-* examples read DMX_IFACE to override the hardcoded interface IP:
DMX_IFACE=192.168.1.50 npm run example/sacn-rainbow
Useful options for DMXController:
protocol: 'sacn' | 'artnet'iface: local interface IPv4 addressreuseAddr: allow multiple listeners/senders on same UDP portunicastDestination: send unicast instead of multicast/broadcastartSync: send ArtSync after flush (Art-Net)RDMnet client options:
uid: required โ this component's RDM UID ({manufacturerId, deviceId}), carried in the Broker Client Entry and every RPT headertransport: 'tcp' | 'tls' (default tcp)tls: Node TLS options (CA, client cert/key, ciphers, min/max version, etc.)requireTlsAuthorization: reject unauthorized TLS peers (default true when transport: 'tls')postConnectAuth: async hook for environment-specific auth/profile checksUse real brokers/devices for compliance verification (not mocks only).
Smoke-runner against a real broker:
RDMNET_INTEROP_HOST=192.168.1.50 \
RDMNET_INTEROP_PORT=5568 \
npm run example/rdmnet-interop-smoke
Environment-gated integration test:
RDMNET_INTEROP_HOST=192.168.1.50 \
RDMNET_INTEROP_PORT=5568 \
RDMNET_INTEROP_SCOPE=default \
npm run test:interop:rdmnet
Useful flags:
RDMNET_INTEROP_TLS=1: use TLS transportRDMNET_INTEROP_TLS_STRICT=0: allow non-authorized TLS peer for lab setupsRDMNET_INTEROP_CHECK_LISTS=1: also validate the broker client-list requestRDMNET_INTEROP_TIMEOUT_MS=5000: per-request timeout overrideTypeDoc output uses README.md as the landing page.
Build docs:
npm run docs:build
Output:
generated/docsiface) is on the same subnet as the fixture.