UNPKG

6.51 kBPlain TextView Raw
1/**
2 * this is the low-level implementation of the E1.31 (sACN) protocol
3 */
4
5import * as assert from 'assert';
6import { objectify, inRange, empty, bit } from './util';
7import {
8 RootVector,
9 ACN_PID,
10 FrameVector,
11 DmpVector,
12 DEFAULT_CID,
13} from './constants';
14
15/* eslint-disable lines-between-class-members, no-bitwise, no-control-regex, camelcase */
16
17export interface Options {
18 universe: Packet['universe'];
19 payload: Packet['payload'];
20 sequence: Packet['sequence'];
21 sourceName?: Packet['sourceName'];
22 priority?: Packet['priority'];
23 cid?: Packet['cid'];
24}
25
26/**
27 * This constructs a sACN Packet, either from an
28 * existing `Buffer` or from `Options`.
29 */
30export class Packet {
31 /* root layer */
32 private readonly root_vector = RootVector.DATA;
33 private readonly root_fl: number;
34 private readonly preambleSize = 0x0010; // =16 (unit16 hence the redundant 00s)
35 private readonly postambleSize = 0;
36 private readonly acnPid = ACN_PID;
37 public readonly cid: Buffer; // unique id of the sender
38
39 /* framing layer */
40 private readonly frame_vector = FrameVector.DATA;
41 private readonly frame_fl: number;
42 public readonly options: number;
43 public readonly sequence: number;
44 public readonly sourceName: string;
45 public readonly priority: number; // 0 to 200; default 100
46 public readonly syncUniverse: number; // universe used for annoucing timesync
47 public readonly universe: number;
48
49 /* DMP layer */
50 private readonly dmp_vector = DmpVector.DATA;
51 private readonly dmp_fl: number;
52 private readonly type = 0xa1; // = 61
53 private readonly firstAddress = 0;
54 private readonly addressIncrement = 1;
55 public readonly propertyValueCount: number;
56 private readonly startCode = 0;
57 private readonly privatePayload: Buffer | Record<number, number>;
58
59 public constructor(
60 input: Buffer | Options,
61 public readonly sourceAddress?: string,
62 ) {
63 if (!input) throw new Error('Buffer packet instantiated with no value');
64 if (input instanceof Buffer) {
65 const buf = input;
66 // If a buffer is supplied, ascertain that the packet implements ACN
67 // correctly, and that is it a data packet. Also asceratain that the
68 // UDP overhead is valid. Then fill up the class values.
69
70 /* root layer */
71 assert.strictEqual(buf.readUInt32BE(18), this.root_vector);
72 this.root_fl = buf.readUInt16BE(16);
73 assert.deepStrictEqual(buf.slice(4, 16), this.acnPid);
74 assert.strictEqual(buf.readUInt16BE(0), this.preambleSize);
75 assert.strictEqual(buf.readUInt16BE(2), this.postambleSize);
76 this.cid = buf.slice(22, 38);
77
78 /* frame layer */
79 assert.strictEqual(buf.readUInt32BE(40), this.frame_vector);
80 this.frame_fl = buf.readUInt16BE(38);
81 this.options = buf.readUInt8(112);
82 this.sequence = buf.readUInt8(111);
83 this.sourceName = buf.toString('ascii', 44, 107).replace(/\x00/g, '');
84 this.priority = buf.readUInt8(108);
85 this.syncUniverse = buf.readUInt16BE(109);
86 this.universe = buf.readUInt16BE(113);
87
88 /* DMP layer */
89 assert.strictEqual(buf.readUInt8(117), this.dmp_vector);
90 this.dmp_fl = buf.readUInt16BE(115);
91 assert.strictEqual(buf.readUInt8(118), this.type);
92 assert.strictEqual(buf.readUInt16BE(119), this.firstAddress);
93 assert.strictEqual(buf.readUInt16BE(121), this.addressIncrement);
94 this.propertyValueCount = buf.readUInt16BE(123);
95 assert.strictEqual(buf.readUInt8(125), this.startCode);
96 this.privatePayload = buf.slice(126);
97 } else {
98 // if input is not a buffer
99 const options = input;
100
101 // set constants
102 this.preambleSize = 0x0010;
103 this.root_fl = 0x726e;
104 this.frame_fl = 0x7258;
105 this.dmp_fl = 0x720b;
106 this.syncUniverse = 0; // we as a sender don't implement this
107 this.options = 0; // TODO: can we just set to 0?
108
109 // set properties
110 this.privatePayload = options.payload;
111 this.sourceName = options.sourceName || 'sACN nodejs';
112 this.priority = options.priority || 100;
113 this.sequence = options.sequence;
114 this.universe = options.universe;
115 this.cid = options.cid || DEFAULT_CID;
116
117 // set computed properties
118 this.propertyValueCount = 0x0201; // "Indicates 1+ the number of slots in packet"
119 // We set the highest possible value (1+512) so that channels with zero values are
120 // treated as deliberately 0 (cf. undefined)
121 }
122 }
123
124 public get payload(): Record<number, number> {
125 return this.privatePayload instanceof Buffer
126 ? objectify(this.privatePayload)
127 : this.privatePayload;
128 }
129
130 public get payloadAsBuffer(): Buffer {
131 return this.privatePayload instanceof Buffer ? this.privatePayload : null;
132 }
133
134 public get buffer(): Buffer {
135 const sourceNameBuf = Buffer.from(this.sourceName.padEnd(64, '\0'));
136 const n: number[] = [].concat(
137 /* root layer */
138 bit(16, this.preambleSize), // 0,1 = preable size
139 bit(16, this.postambleSize), // 2,3 = postamble size
140 [...this.acnPid],
141 bit(16, this.root_fl), // 16,17 = root fl
142 bit(32, this.root_vector), // 18,19,20,21 = Root_vector
143 [...this.cid], // 22-37 = cid
144
145 /* framing layer */
146 bit(16, this.frame_fl), // 38,39 = frame fl
147 bit(32, this.frame_vector), // 40,41,42,43 = frame vector
148 [...sourceNameBuf], // 44 - 107 = sourceName
149 bit(8, this.priority), // 108 = priority (8bit)
150 bit(16, this.syncUniverse), // 109,110 = syncUniverse
151 bit(8, this.sequence), // 111 = sequence
152 bit(8, this.options), // 112 = options
153 bit(16, this.universe), // 113,114 = universe
154
155 /* DMP layer */
156 bit(16, this.dmp_fl), // 115,116 = dmp_fl
157 bit(8, this.dmp_vector), // 117 = dmp vector
158 bit(8, this.type), // 118 = type
159 bit(16, this.firstAddress), // 119,120 = first adddress
160 bit(16, this.addressIncrement), // 121,122 = addressIncrement
161 bit(16, this.propertyValueCount), // 123,124 = propertyValueCount
162 bit(8, this.startCode), // 125 = startCode
163 empty(512), // 126-638 = dmx channels 1-512
164 );
165
166 for (const ch in this.payload) {
167 if (+ch >= 1 && +ch <= 512) {
168 n[125 + +ch] = inRange(this.payload[ch] * 2.55);
169 }
170 }
171
172 return Buffer.from(n);
173 }
174
175 // TODO: For octet 112 (options): Bit 7 = Preview_Data / Bit 6 = Stream_Terminated / Bit 5 = Force_Synchronization
176 // public getOption(option: number): boolean {
177 // return !!(this.options & (1 << (option % 8)));
178 // }
179}