-
Notifications
You must be signed in to change notification settings - Fork 10
/
sender.ts
118 lines (99 loc) 路 3.4 KB
/
sender.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { Socket, createSocket } from 'dgram';
import { multicastGroup } from './util';
import { Packet, Options } from './packet';
export interface SenderProps {
universe: number;
port?: number;
reuseAddr?: boolean;
/**
* How often the data should be re-sent (**in Hertz/Hz**), even if it hasn't changed.
*
* By default data will only be sent once (equivilant of setting `refreshRate: 0`).
*
* To re-send data 5 times per second (`5Hz`), set `refreshRate: 5`. This is equivilant to `200ms`.
*/
minRefreshRate?: number;
/** some options can be sepecified when you instantiate the sender, instead of sepecifying them on every packet */
defaultPacketOptions?: Pick<
Options,
'cid' | 'sourceName' | 'priority' | 'useRawDmxValues'
>;
// IPv4 address of the network interface
iface?: string;
/**
* If you set this option to an IP address, then data will be sent
* purely to this address, instead of the whole network.
*
* This option is not recommended and may not be supported by all devices.
*/
useUnicastDestination?: string;
}
export class Sender {
private socket: Socket;
private readonly port: SenderProps['port'];
public readonly universe: SenderProps['universe'];
/**
* this is normally a multicast address, but it could be
* a unicast address if the user configures `useUnicastDestination`
*/
readonly #destinationIp: string;
private readonly defaultPacketOptions: SenderProps['defaultPacketOptions'];
private sequence = 0;
#loopId: NodeJS.Timeout | undefined;
/**
* we keep track of the most recent value of every channel, so that we can
* send it regulally if `refreshRate` != 0. `undefined` if nothing has been
* sent yet.
*/
#latestPacketOptions: Omit<Options, 'sequence' | 'universe'> | undefined;
constructor({
universe,
port = 5568,
reuseAddr = false,
minRefreshRate = 0,
defaultPacketOptions,
iface,
useUnicastDestination,
}: SenderProps) {
this.port = port;
this.universe = universe;
this.#destinationIp = useUnicastDestination || multicastGroup(universe);
this.defaultPacketOptions = defaultPacketOptions;
this.socket = createSocket({ type: 'udp4', reuseAddr });
if (iface || reuseAddr) {
// prevent different behavior due to socket.bind() side effects, but binding the socket when reuseAddr: false could cause problems
this.socket.bind(port, () => {
// need to bind socket first
if (iface) {
this.socket.setMulticastInterface(iface);
}
});
}
if (minRefreshRate) {
this.#loopId = setInterval(() => this.reSend(), 1000 / minRefreshRate);
}
}
public send(packet: Omit<Options, 'sequence' | 'universe'>): Promise<void> {
const finalPacket = { ...this.defaultPacketOptions, ...packet };
this.#latestPacketOptions = finalPacket;
return new Promise((resolve, reject) => {
const { buffer } = new Packet({
...finalPacket,
universe: this.universe,
sequence: this.sequence,
});
this.sequence = (this.sequence + 1) % 256;
this.socket.send(buffer, this.port, this.#destinationIp, (err) =>
err ? reject(err) : resolve(),
);
});
}
private reSend() {
if (this.#latestPacketOptions) this.send(this.#latestPacketOptions);
}
public close(): this {
if (this.#loopId) clearTimeout(this.#loopId);
this.socket.close();
return this;
}
}