191 lines
5.2 KiB
TypeScript

import HID from "node-hid";
/*
Return codes
* 0: OK
* 1: invalid channel
* 2: invalid value
* 3: invalid mode
* 4: invalid array size
* 5: other error
*/
/**
* Represents a connected DMX interface and handles all communication with it
*/
class DMXInterface {
path: string;
serial: string;
manufacturer: string | undefined;
product: string | undefined;
currentMode = 0;
hidDevice: HID.HID;
dmxout: number[];
usbdmxInputCallback: (start: number, values: number[]) => void;
constructor(
path: string,
serial: string,
manufacturer: string | undefined = undefined,
product: string | undefined = undefined
) {
this.path = path;
this.serial = serial;
this.manufacturer = manufacturer;
this.product = product;
this.hidDevice = new HID.HID(path);
this.hidDevice.on("data", (data: Buffer) => {
// received buffer contains 33 bytes, the first one (data[0]) is the page and the rest are the dmx channel values
// this means we get 32 dmx channels in one package
const values: number[] = [];
for (let i = 1; i < 33; i++) {
values.push(data[i]);
}
this.usbdmxInputCallback(data[0] * 32, values);
})
this.dmxout = Array(512).fill(0);
}
/**
* Opens the interface and returns {@link DMXInterface}
* @param path HID path of the interface
* @param serial Serial number
* @param manufacturer Manufacturer ID
* @param product Product ID
*/
static open = (
path: string,
serial: string,
manufacturer: string | undefined = undefined,
product: string | undefined = undefined
): Promise<DMXInterface> => {
return new Promise<DMXInterface>((resolve) => {
resolve(new DMXInterface(path, serial, manufacturer, product));
});
}
/**
* Closes the HID connection to the interface
*/
close = () => {
this.setMode(0);
this.hidDevice.close();
}
/*
Description of how DMX + HID works:
Each HID buffer sent contains 33 bytes.
The first byte (buffer[0]) is the "page", the rest are 32 DMX channels.
To get to the other DMX channels, increase the first byte
e.g. DMX channel = 33 -> buffer[0] = 1; buffer[1] = value for ch. 33
* Buffer values
* Index 0 (buffer[0])
* - 0-15: DMX?
* - 16: Mode
* - 17: Config?
*
*/
/*
* Modes:
* 0: Do nothing - Standby
* 1: DMX In -> DMX Out
* 2: PC Out -> DMX Out
* 3: DMX In + PC Out -> DMX Out
* 4: DMX In -> PC In
* 5: DMX In -> DMX Out & DMX In -> PC In
* 6: PC Out -> DMX Out & DMX In -> PC In
* 7: DMX In + PC Out -> DMX Out & DMX In -> PC In
*/
/**
* Sets the DMX mode of the interface
* @param mode mode as a number from 0-7
*/
setMode = (mode: number): number => {
// check if mode is between 0 and 7
if (mode > 7 || mode < 0) return 3;
// create data buffer
const _buffer = Buffer.alloc(34);
_buffer[1] = 16;
_buffer[2] = mode;
try {
this.hidDevice.write(_buffer);
this.currentMode = mode;
return 0;
}
catch (err) {
console.log(err);
return 5;
}
}
/**
* Writes DMX data to the interface
* @param data DMX data object array
*/
write = (data: DMXCommand[] | undefined): number => {
let returnStatus = 0;
if (data !== undefined) {
// update dmx out array with new values
for (const _entry of data) {
if (_entry.channel < 1 || _entry.channel > 512) {
returnStatus = 1;
continue;
}
if (_entry.value < 0 || _entry.value > 255) {
returnStatus = 2;
continue;
}
this.dmxout[_entry.channel - 1] = _entry.value
}
}
// loop through the 16 "pages" of commands (each write command can hold 32 channels)
for (let i = 0; i < 16; i++) {
const _buffer = Buffer.alloc(34);
// first byte needs to be 0 according to node-hid documentation (reportId)
_buffer[0] = 0x00;
// set second byte to page number
_buffer[1] = i;
for (let j = 2; j < 34; j++) {
// get value for corresponding channel (i * 32 for the page)
_buffer[j] = this.dmxout[(i * 32) + j - 2];
}
this.hidDevice.write(_buffer);
}
return returnStatus;
}
/**
* Writes an entire universe to the interface
* @param array Array of all DMX values with a length of 512
*/
writeMap = (array: number[]): number => {
if (array.length !== 512) return 4;
this.dmxout = array;
this.write(undefined);
return 0;
}
}
/**
* Data structure containing a pair of a DMX channel and its value
*/
interface DMXCommand {
channel: number;
value: number;
}
export { DMXInterface, DMXCommand };