mirror of
https://github.com/moritzruth/node-enttec-open-dmx-usb.git
synced 2025-04-21 07:41:22 +02:00
Rewrite using TypeScript and Pika Pack
This commit is contained in:
parent
3b9822607f
commit
967d07b020
9 changed files with 3151 additions and 842 deletions
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"extends": "@moritzruth",
|
||||
"extends": "awzzm-ts",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 10,
|
||||
"project": "tsconfig.json"
|
||||
}
|
||||
}
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
node_modules/
|
||||
pkg/
|
||||
|
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
10
|
|
@ -1,16 +1,16 @@
|
|||
# node-enttec-open-dmx-usb
|
||||
> A Node.js library for sending DMX data through the
|
||||
>[Enttec Open DMX USB Interface](https://www.enttec.co.uk/en/product/controls/dmx-usb-interfaces/open-dmx-usb/)
|
||||
> A Node.js library for interacting with the
|
||||
> [Enttec Open DMX USB Interface](https://www.enttec.co.uk/en/product/controls/dmx-usb-interfaces/open-dmx-usb/)
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
```shell script
|
||||
yarn add enttec-open-dmx-usb
|
||||
# or
|
||||
npm install enttec-open-dmx-usb
|
||||
```
|
||||
|
||||
## Usage
|
||||
All functions are documented using JSDoc and the code is not uglified, so feel free to [explore it](index.js).
|
||||
All functions are documented using JSDoc, and the code is not uglified and easy to understand, so feel free to [explore it](src/index.ts).
|
||||
|
||||
```js
|
||||
import { EnttecOpenDMXUSBDevice as DMXDevice } from "enttec-open-dmx-usb";
|
||||
|
|
174
index.js
174
index.js
|
@ -1,174 +0,0 @@
|
|||
import EventEmitter from "events";
|
||||
import SerialPort from "serialport";
|
||||
import _ from "lodash";
|
||||
|
||||
export const VENDOR_ID = "0403"; // Enttec
|
||||
export const PRODUCT_ID = "6001"; // Open DMX USB
|
||||
|
||||
export class EnttecOpenDMXUSBDevice extends EventEmitter {
|
||||
/**
|
||||
* @param {string} path A path returned by {@link EnttecOpenDMXUSBDevice.listDevices} or
|
||||
* {@link EnttecOpenDMXUSBDevice.getFirstAvailableDevice}.
|
||||
* @param {boolean} [startSending=true] If the device should start sending as soon as it is ready.
|
||||
*/
|
||||
constructor (path, startSending = true) {
|
||||
super();
|
||||
|
||||
if (typeof path !== "string") {
|
||||
throw new TypeError("path has to be of type string.");
|
||||
}
|
||||
|
||||
this._shouldBeSending = false;
|
||||
this._sendTimeoutID = null;
|
||||
this._buffer = Buffer.alloc(513);
|
||||
|
||||
this.port = new SerialPort(path, {
|
||||
baudRate: 250000,
|
||||
dataBits: 8,
|
||||
stopBits: 2,
|
||||
parity: "none",
|
||||
autoOpen: true
|
||||
});
|
||||
|
||||
this.port.on("open", () => {
|
||||
this.emit("ready");
|
||||
if (startSending) {
|
||||
this.startSending(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts sending.
|
||||
* @param {number} [interval=0] The time between each attempt to send.
|
||||
* @throws Error If the device is not ready yet.
|
||||
*/
|
||||
startSending (interval = 0) {
|
||||
if (!this.port.isOpen) {
|
||||
throw new Error("The device is not ready yet. Wait for the 'ready' event.");
|
||||
}
|
||||
|
||||
this.emit("startSending", interval);
|
||||
this._shouldBeSending = true;
|
||||
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const send = async () => {
|
||||
await this._sendUniverse();
|
||||
if (this._shouldBeSending) {
|
||||
this._sendTimeoutID = setTimeout(send, interval);
|
||||
}
|
||||
};
|
||||
|
||||
send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops sending.
|
||||
*/
|
||||
stopSending () {
|
||||
this.emit("stopSending");
|
||||
this._shouldBeSending = false;
|
||||
|
||||
if (this._sendTimeoutID !== null) {
|
||||
clearTimeout(this._sendTimeoutID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel values.
|
||||
* If channels is an Object, the keys are the channel numbers.
|
||||
*
|
||||
* @param {Buffer|Object|Array} channels
|
||||
* @param {boolean} [clear=false] If all previously assigned channels should be set to 0
|
||||
*/
|
||||
setChannels (channels, clear = false) {
|
||||
if (clear) {
|
||||
this._buffer = Buffer.alloc(513);
|
||||
this._buffer[0] = 0;
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(channels)) {
|
||||
if (channels.length > 512) {
|
||||
throw new Error("The maximum size of an DMX universe is 512 channels.");
|
||||
}
|
||||
|
||||
channels.copy(this._buffer, 1);
|
||||
} else if (Array.isArray(channels)) {
|
||||
if (channels.length > 512) {
|
||||
throw new Error("The maximum size of an DMX universe is 512 channels.");
|
||||
}
|
||||
|
||||
channels.forEach((value, index) => {
|
||||
if (value > 0xFF || value < 0) {
|
||||
throw new Error("All values must be between 0 and 255.");
|
||||
}
|
||||
|
||||
this._buffer[index + 1] = value;
|
||||
});
|
||||
} else if (typeof channels === "object") {
|
||||
Object.entries(channels).forEach(([channel, value]) => {
|
||||
let channelNumber;
|
||||
|
||||
try {
|
||||
channelNumber = parseInt(channel);
|
||||
} catch {
|
||||
throw new Error("Only channel numbers are supported.");
|
||||
}
|
||||
|
||||
if (channelNumber > 512 || channelNumber < 1) {
|
||||
throw new Error("All channel numbers must be between 1 and 512.");
|
||||
} else if (value > 0xFF || value < 0) {
|
||||
throw new Error("All values must be between 0 and 255.");
|
||||
}
|
||||
|
||||
this._buffer[channelNumber] = value;
|
||||
});
|
||||
} else {
|
||||
throw new TypeError("data must be of type Buffer, Object or Array.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise} Resolves when the whole universe was send.
|
||||
* @private
|
||||
*/
|
||||
_sendUniverse () {
|
||||
return new Promise(resolve => {
|
||||
this.port.set({ brk: true, rts: false }, () => {
|
||||
setTimeout(() => {
|
||||
this.port.set({ brk: false, rts: false }, () => {
|
||||
setTimeout(() => {
|
||||
this.port.write(this._buffer, () => resolve());
|
||||
}, 0);
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the paths of all available devices.
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
static async listDevices () {
|
||||
const allPorts = await SerialPort.list();
|
||||
return allPorts
|
||||
.filter(x => _.matches({ vendorId: VENDOR_ID, productId: PRODUCT_ID })(x))
|
||||
.map(x => _.property("path")(x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the first available device found.
|
||||
* @throws Error when no device is found.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
static async getFirstAvailableDevice () {
|
||||
const devices = await EnttecOpenDMXUSBDevice.listDevices();
|
||||
|
||||
if (devices.length === 0) {
|
||||
throw new Error("No device found.");
|
||||
} else {
|
||||
return devices[0];
|
||||
}
|
||||
}
|
||||
}
|
38
package.json
38
package.json
|
@ -1,22 +1,36 @@
|
|||
{
|
||||
"name": "enttec-open-dmx-usb",
|
||||
"version": "1.0.1",
|
||||
"description": "A Node.js library for sending DMX data through the Enttec Open DMX USB Interface",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"description": "A Node.js library for interacting with the Enttec Open DMX USB Interface",
|
||||
"main": "src/index.js",
|
||||
"repository": "https://github.com/moritzruth/node-enttec-open-dmx-usb.git",
|
||||
"author": "Moritz Ruth <dev@moritz-ruth.de>",
|
||||
"author": "Moritz Ruth <dev@moritzruth.de>",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"index.js"
|
||||
"scripts": {
|
||||
"build": "pika build",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"@pika/pack": {
|
||||
"pipeline": [
|
||||
[
|
||||
"@pika/plugin-ts-standard-pkg"
|
||||
],
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15",
|
||||
"serialport": "^9.0.0"
|
||||
[
|
||||
"@pika/plugin-build-node"
|
||||
]
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@moritzruth/eslint-config": "^1.0.6",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"eslint": "^6.8.0"
|
||||
"@pika/pack": "^0.5.0",
|
||||
"@pika/plugin-build-node": "^0.9.2",
|
||||
"@pika/plugin-ts-standard-pkg": "^0.9.2",
|
||||
"@types/node": "^14.0.27",
|
||||
"@types/serialport": "^8.0.1",
|
||||
"eslint": "^7.6.0",
|
||||
"eslint-config-awzzm-ts": "^1.0.1",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"serialport": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
|
153
src/index.ts
Normal file
153
src/index.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import { EventEmitter } from "events"
|
||||
import SerialPort from "serialport"
|
||||
|
||||
export const VENDOR_ID = "0403" // Enttec
|
||||
export const PRODUCT_ID = "6001" // Open DMX USB
|
||||
|
||||
export class EnttecOpenDMXUSBDevice extends EventEmitter {
|
||||
private shouldBeSending = false
|
||||
private sendTimeout: NodeJS.Timeout | null = null
|
||||
private buffer = Buffer.alloc(513)
|
||||
private readonly port: SerialPort
|
||||
|
||||
/**
|
||||
* @param {string} path A path returned by {@link EnttecOpenDMXUSBDevice.listDevices} or
|
||||
* {@link EnttecOpenDMXUSBDevice.getFirstAvailableDevice}.
|
||||
* @param {boolean} [startSending=true] If the device should start sending as soon as it is ready.
|
||||
*/
|
||||
constructor(path: string, startSending = true) {
|
||||
super()
|
||||
|
||||
this.port = new SerialPort(path, {
|
||||
baudRate: 250000,
|
||||
dataBits: 8,
|
||||
stopBits: 2,
|
||||
parity: "none",
|
||||
autoOpen: true
|
||||
})
|
||||
|
||||
this.port.on("open", () => {
|
||||
this.emit("ready")
|
||||
if (startSending) this.startSending(0)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts sending.
|
||||
* @param {number} [interval=0] The time between each attempt to send.
|
||||
* @throws Error If the device is not ready yet.
|
||||
*/
|
||||
startSending(interval = 0) {
|
||||
if (!this.port.isOpen) throw new Error("The device is not ready yet. Wait for the 'ready' event.")
|
||||
|
||||
this.emit("sending-started", interval)
|
||||
this.shouldBeSending = true
|
||||
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const send = () => {
|
||||
this.sendUniverse()
|
||||
.then(() => {
|
||||
if (this.shouldBeSending)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.sendTimeout = setTimeout(send, interval)
|
||||
})
|
||||
.catch(error => this.emit("error", error))
|
||||
}
|
||||
|
||||
send()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops sending.
|
||||
*/
|
||||
stopSending() {
|
||||
this.emit("sending-stopped")
|
||||
this.shouldBeSending = false
|
||||
|
||||
if (this.sendTimeout !== null) clearTimeout(this.sendTimeout)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel values.
|
||||
* If channels is an Object, the keys are the channel numbers.
|
||||
*
|
||||
* @param {Buffer|Object|Array} channels
|
||||
* @param {boolean} [clear=false] Whether all previously assigned channels should be set to 0
|
||||
*/
|
||||
setChannels(channels: Buffer | number[] | { [key: number]: number }, clear = false) {
|
||||
if (clear) {
|
||||
this.buffer = Buffer.alloc(513)
|
||||
this.buffer[0] = 0
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(channels)) {
|
||||
if (channels.length > 512) throw new Error("The maximum size of an DMX universe is 512 channels.")
|
||||
channels.copy(this.buffer, 1)
|
||||
} else if (Array.isArray(channels)) {
|
||||
if (channels.length > 512) throw new Error("The maximum size of an DMX universe is 512 channels.")
|
||||
|
||||
channels.forEach((value, index) => {
|
||||
if (value > 0xFF || value < 0) throw new Error("All values must be between 0 and 255.")
|
||||
this.buffer[index + 1] = value
|
||||
})
|
||||
} else if (typeof channels === "object") {
|
||||
Object.entries(channels).forEach(([channel, value]) => {
|
||||
let channelNumber
|
||||
|
||||
try {
|
||||
channelNumber = Number.parseInt(channel, 10)
|
||||
} catch {
|
||||
throw new Error("Only channel numbers are supported.")
|
||||
}
|
||||
|
||||
if (channelNumber > 512 || channelNumber < 1)
|
||||
throw new Error("All channel numbers must be between 1 and 512.")
|
||||
else if (value > 0xFF || value < 0)
|
||||
throw new Error("All values must be between 0 and 255.")
|
||||
|
||||
this.buffer[channelNumber] = value
|
||||
})
|
||||
} else throw new TypeError("data must be of type Buffer, Object or Array.")
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise} Resolves when the whole universe was send.
|
||||
* @private
|
||||
*/
|
||||
private async sendUniverse(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
this.port.set({ brk: true, rts: false }, () => {
|
||||
setTimeout(() => {
|
||||
this.port.set({ brk: false, rts: false }, () => {
|
||||
setTimeout(() => {
|
||||
this.port.write(this.buffer, () => resolve())
|
||||
}, 0)
|
||||
})
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the paths of all available devices.
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
static async listDevices(): Promise<string[]> {
|
||||
const allPorts = await SerialPort.list()
|
||||
return allPorts
|
||||
.filter(device => device.vendorId === VENDOR_ID && device.productId === PRODUCT_ID)
|
||||
.map(device => device.path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the first available device found.
|
||||
* @throws Error when no device is found.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
static async getFirstAvailableDevice(): Promise<string> {
|
||||
const devices = await EnttecOpenDMXUSBDevice.listDevices()
|
||||
|
||||
if (devices.length === 0) throw new Error("No device found.")
|
||||
else return devices[0]
|
||||
}
|
||||
}
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue