├── .travis.yml ├── demo_socket_client.js ├── .editorconfig ├── .gitignore ├── demo_simple.js ├── package.json ├── test └── anim.test.js ├── LICENSE ├── drivers ├── null.js ├── sacn.js ├── socketio.js ├── bbdmx.js ├── dmx4all.js ├── enttec-open-usb-dmx.js ├── artnet.js ├── enttec-usb-dmx-pro.js └── dmxking-ultra-dmx-pro.js ├── index.js ├── demo.js ├── anim.js ├── .eslintrc ├── readme.md ├── easing.js └── devices.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | 5 | -------------------------------------------------------------------------------- /demo_socket_client.js: -------------------------------------------------------------------------------- 1 | const port = 17809; 2 | const io = require('socket.io-client'); 3 | const client = io.connect(`http://localhost:${port}`); 4 | 5 | client.on('update', (msg) => console.info(msg)); 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = LF 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | .DS_Store 14 | 15 | node_modules 16 | .project 17 | .com.greenworldsoft.syncfolderspro 18 | 19 | # Remove some common IDE working directories 20 | .idea 21 | .vscode 22 | 23 | .DS_Store 24 | -------------------------------------------------------------------------------- /demo_simple.js: -------------------------------------------------------------------------------- 1 | const DMX = require('./index'); 2 | 3 | const dmx = new DMX(); 4 | 5 | // var universe = dmx.addUniverse('demo', 'enttec-open-usb-dmx', '/dev/cu.usbserial-6AVNHXS8') 6 | // const universe = dmx.addUniverse('demo', 'socketio', null, {port: 17809, debug: true}); 7 | const universe = dmx.addUniverse('demo', 'null'); 8 | 9 | let on = false; 10 | 11 | setInterval(() => { 12 | if (on) { 13 | on = false; 14 | universe.updateAll(0); 15 | console.log('off'); 16 | } else { 17 | on = true; 18 | universe.updateAll(250); 19 | console.log('on'); 20 | } 21 | }, 1000); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dmx", 3 | "version": "0.25.1", 4 | "author": "Sebastian Wiedenroth ", 5 | "description": "A nodejs DMX library", 6 | "url": "https://github.com/node-dmx/dmx", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/node-dmx/dmx.git" 10 | }, 11 | "keywords": [ 12 | "DMX", 13 | "light control" 14 | ], 15 | "dependencies": { 16 | "sacn": "^3.2.1", 17 | "serialport": "^11.0.1", 18 | "socket.io": "^4.8.1" 19 | }, 20 | "license": "MIT", 21 | "engines": { 22 | "node": ">=10.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/anim.test.js: -------------------------------------------------------------------------------- 1 | const DMX = require('dmx'); 2 | 3 | const dmx = new DMX(); 4 | const universe = dmx.addUniverse('test', 'null', 'test'); 5 | 6 | const updateMock = jest.fn(); 7 | 8 | universe.update = updateMock; 9 | universe.update({ 1: 255 }); 10 | 11 | const ANIM_PRECISION = 50; 12 | 13 | test('fake timers', () => { 14 | jest.useFakeTimers(); 15 | 16 | new DMX.Animation().add({ 17 | 1: 255, 18 | }, 100).add({ 19 | 1: 0, 20 | }, 100).run(universe); 21 | 22 | jest.runAllTimers(); 23 | 24 | expect(updateMock).toHaveBeenCalledWith({ 1: 255 }, { origin: 'animation' }); 25 | expect(updateMock).toHaveBeenCalledWith({ 1: 0 }, { origin: 'animation' }); 26 | }); 27 | 28 | test('real timers', done => { 29 | jest.useRealTimers(); 30 | 31 | const startAt = Date.now(); 32 | 33 | new DMX.Animation().add({ 34 | 1: 255, 35 | }, 250).add({ 36 | 1: 0, 37 | }, 250).run(universe, () => { 38 | const timeTook = Date.now() - startAt; 39 | 40 | expect(timeTook).toBeGreaterThanOrEqual(500 - ANIM_PRECISION); 41 | expect(timeTook).toBeLessThanOrEqual(500 + ANIM_PRECISION); 42 | done(); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012-2018 Sebastian Wiedenroth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /drivers/null.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const EventEmitter = require('events').EventEmitter; 3 | 4 | function NullDriver(deviceId, options = {}) { 5 | this.universe = Buffer.alloc(513, 0); 6 | this.interval = 1000 / (options.dmx_speed || 1); 7 | this.start(); 8 | } 9 | 10 | NullDriver.prototype.start = function () { 11 | this.timeout = setInterval(() => { 12 | this.logUniverse(); 13 | }, this.interval); 14 | }; 15 | 16 | NullDriver.prototype.stop = function () { 17 | clearInterval(this.timeout); 18 | }; 19 | 20 | NullDriver.prototype.close = cb => { 21 | cb(null); 22 | }; 23 | 24 | NullDriver.prototype.update = function (u, extraData) { 25 | for (const c in u) { 26 | this.universe[c] = u[c]; 27 | } 28 | this.logUniverse(); 29 | 30 | this.emit('update', u, extraData); 31 | }; 32 | 33 | NullDriver.prototype.updateAll = function (v, _) { 34 | for (let i = 1; i <= 512; i++) { 35 | this.universe[i] = v; 36 | } 37 | }; 38 | 39 | NullDriver.prototype.get = function (c) { 40 | return this.universe[c]; 41 | }; 42 | 43 | NullDriver.prototype.logUniverse = function () { 44 | console.log(this.universe.slice(1)); 45 | }; 46 | 47 | util.inherits(NullDriver, EventEmitter); 48 | 49 | module.exports = NullDriver; 50 | -------------------------------------------------------------------------------- /drivers/sacn.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | const util = require('util'); 3 | const sacn = require('sacn'); 4 | 5 | function SACNDriver(deviceId, options = {}) { 6 | this.sACNServer = new sacn.Sender({ 7 | universe: options.universe || 1, 8 | reuseAddr: true, 9 | }); 10 | this.universe = {}; 11 | } 12 | 13 | SACNDriver.prototype.start = function () {}; 14 | 15 | SACNDriver.prototype.stop = function () { 16 | this.sACNServer.close(); 17 | }; 18 | 19 | SACNDriver.prototype.close = function (cb) { 20 | this.stop(); 21 | cb(null); 22 | }; 23 | 24 | SACNDriver.prototype.update = function (u, extraData) { 25 | for (const c in u) { 26 | this.universe[c] = this.dmxToPercent(u[c]); 27 | } 28 | this.sendUniverse(); 29 | }; 30 | 31 | SACNDriver.prototype.sendUniverse = function () { 32 | this.sACNServer.send({ 33 | payload: this.universe, 34 | }); 35 | }; 36 | 37 | SACNDriver.prototype.updateAll = function (v, _) { 38 | for (let i = 1; i <= 512; i++) { 39 | this.universe[i] = this.dmxToPercent(v); 40 | } 41 | this.sendUniverse(); 42 | }; 43 | 44 | SACNDriver.prototype.get = function (c) { 45 | return this.percentToDmx(this.universe[c]); 46 | }; 47 | 48 | SACNDriver.prototype.dmxToPercent = function (v) { 49 | return v / 255 * 100; 50 | }; 51 | 52 | SACNDriver.prototype.percentToDmx = function (v) { 53 | return v / 100 * 255; 54 | }; 55 | 56 | util.inherits(SACNDriver, EventEmitter); 57 | 58 | module.exports = SACNDriver; 59 | -------------------------------------------------------------------------------- /drivers/socketio.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const EventEmitter = require('events').EventEmitter; 3 | 4 | function SocketioDriver(deviceId, options) { 5 | options = options || {}; 6 | 7 | const self = this; 8 | const { Server } = require("socket.io"); 9 | const port = options.port || 18909; 10 | const debug = options.debug || false; 11 | 12 | this.server = new Server(port); 13 | this.server.on('connection', (socket) => { 14 | if (debug) console.info(`Client connected [id=${socket.id}]`); 15 | socket.on('disconnect', () => { 16 | if (debug) console.info(`Client gone [id=${socket.id}]`); 17 | }); 18 | }); 19 | 20 | this.universe = Buffer.alloc(513, 0); 21 | self.start(); 22 | } 23 | 24 | SocketioDriver.prototype.start = function () {}; 25 | 26 | SocketioDriver.prototype.stop = function () { 27 | clearInterval(this.timeout); 28 | }; 29 | 30 | SocketioDriver.prototype.close = cb => { 31 | cb(null); 32 | }; 33 | 34 | SocketioDriver.prototype.update = function (u, extraData) { 35 | for (const c in u) { 36 | this.universe[c] = u[c]; 37 | } 38 | this.server.sockets.emit('update', [...this.universe]); 39 | this.emit('update', u, extraData); 40 | }; 41 | 42 | SocketioDriver.prototype.updateAll = function (v) { 43 | for (let i = 1; i <= 512; i++) { 44 | this.universe[i] = v; 45 | } 46 | 47 | this.server.sockets.emit('update', [...this.universe]); 48 | }; 49 | 50 | SocketioDriver.prototype.get = function (c) { 51 | return this.universe[c]; 52 | }; 53 | 54 | util.inherits(SocketioDriver, EventEmitter); 55 | 56 | module.exports = SocketioDriver; 57 | -------------------------------------------------------------------------------- /drivers/bbdmx.js: -------------------------------------------------------------------------------- 1 | const dgram = require('dgram'); 2 | const util = require('util'); 3 | const EventEmitter = require('events').EventEmitter; 4 | 5 | const UNIVERSE_LEN = 512; 6 | 7 | function BBDMX(deviceId = '127.0.0.1', options = {}) { 8 | this.readyToWrite = true; 9 | this.interval = options.dmx_speed ? (1000 / options.dmx_speed) : 24; 10 | this.options = options; 11 | this.universe = Buffer.alloc(UNIVERSE_LEN + 1); 12 | this.host = deviceId; 13 | this.port = options.port || 9930; 14 | this.dev = dgram.createSocket('udp4'); 15 | this.start(); 16 | } 17 | 18 | BBDMX.prototype.sendUniverse = function () { 19 | if (this.readyToWrite) { 20 | this.readyToWrite = false; 21 | 22 | let channel; 23 | let messageBuffer = Buffer.from(UNIVERSE_LEN.toString()); 24 | 25 | for (let i = 1; i <= UNIVERSE_LEN; i++) { 26 | channel = Buffer.from(' ' + this.universe[i]); 27 | messageBuffer = Buffer.concat([messageBuffer, channel]); 28 | } 29 | 30 | this.dev.send(messageBuffer, 0, messageBuffer.length, this.port, this.host, () => { 31 | this.readyToWrite = true; 32 | }); 33 | } 34 | }; 35 | 36 | BBDMX.prototype.start = function () { 37 | this.interval = setInterval(this.sendUniverse.bind(this), this.interval); 38 | }; 39 | 40 | BBDMX.prototype.stop = function () { 41 | clearInterval(this.interval); 42 | }; 43 | 44 | BBDMX.prototype.close = function (cb) { 45 | this.stop(); 46 | cb(null); 47 | }; 48 | 49 | BBDMX.prototype.update = function (u, extraData) { 50 | for (const c in u) { 51 | this.universe[c] = u[c]; 52 | } 53 | 54 | this.emit('update', u, extraData); 55 | }; 56 | 57 | BBDMX.prototype.updateAll = function (v) { 58 | for (let i = 1; i <= UNIVERSE_LEN; i++) { 59 | this.universe[i] = v; 60 | } 61 | }; 62 | 63 | BBDMX.prototype.get = function (c) { 64 | return this.universe[c]; 65 | }; 66 | 67 | util.inherits(BBDMX, EventEmitter); 68 | 69 | module.exports = BBDMX; 70 | -------------------------------------------------------------------------------- /drivers/dmx4all.js: -------------------------------------------------------------------------------- 1 | const { SerialPort } = require('serialport'); 2 | const util = require('util'); 3 | const EventEmitter = require('events').EventEmitter; 4 | 5 | const UNIVERSE_LEN = 512; 6 | 7 | function DMX4ALL(deviceId, options = {}) { 8 | this.universe = Buffer.alloc(UNIVERSE_LEN + 1); 9 | this.readyToWrite = true; 10 | this.interval = 1000 / (options.dmx_speed || 33); 11 | 12 | this.dev = new SerialPort({ 13 | 'path': deviceId, 14 | 'baudRate': 38400, 15 | 'dataBits': 8, 16 | 'stopBits': 1, 17 | 'parity': 'none', 18 | }, err => { 19 | if (!err) { 20 | this.start(); 21 | } else { 22 | console.warn(err); 23 | } 24 | }); 25 | // this.dev.on('data', data => { 26 | // process.stdout.write(data.toString('ascii')) 27 | // }); 28 | } 29 | 30 | DMX4ALL.prototype.sendUniverse = function () { 31 | if (!this.dev.writable) { 32 | return; 33 | } 34 | 35 | if (this.readyToWrite) { 36 | this.readyToWrite = false; 37 | 38 | const msg = Buffer.alloc(UNIVERSE_LEN * 3); 39 | 40 | for (let i = 0; i < UNIVERSE_LEN; i++) { 41 | msg[i * 3 + 0] = (i < 256) ? 0xE2 : 0xE3; 42 | msg[i * 3 + 1] = i; 43 | msg[i * 3 + 2] = this.universe[i + 1]; 44 | } 45 | 46 | this.dev.write(msg); 47 | this.dev.drain(() => { 48 | this.readyToWrite = true; 49 | }); 50 | } 51 | }; 52 | 53 | DMX4ALL.prototype.start = function () { 54 | this.intervalhandle = setInterval(this.sendUniverse.bind(this), this.interval); 55 | }; 56 | 57 | DMX4ALL.prototype.stop = function () { 58 | clearInterval(this.intervalhandle); 59 | }; 60 | 61 | DMX4ALL.prototype.close = function (cb) { 62 | this.dev.close(cb); 63 | }; 64 | 65 | DMX4ALL.prototype.update = function (u, extraData) { 66 | for (const c in u) { 67 | this.universe[c] = u[c]; 68 | } 69 | 70 | this.emit('update', u, extraData); 71 | }; 72 | 73 | DMX4ALL.prototype.updateAll = function (v) { 74 | for (let i = 1; i <= 512; i++) { 75 | this.universe[i] = v; 76 | } 77 | }; 78 | 79 | DMX4ALL.prototype.get = function (c) { 80 | return this.universe[c]; 81 | }; 82 | 83 | util.inherits(DMX4ALL, EventEmitter); 84 | 85 | module.exports = DMX4ALL; 86 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const EventEmitter = require('events').EventEmitter; 3 | 4 | class DMX { 5 | constructor(options) { 6 | const opt = options || {}; 7 | const devices = opt.devices || {}; 8 | 9 | this.universes = {}; 10 | this.drivers = {}; 11 | this.devices = Object.assign({}, require('./devices'), devices); 12 | this.animation = require('./anim'); 13 | 14 | this.registerDriver('null', require('./drivers/null')); 15 | this.registerDriver('socketio', require('./drivers/socketio')); 16 | this.registerDriver('dmx4all', require('./drivers/dmx4all')); 17 | this.registerDriver('enttec-usb-dmx-pro', require('./drivers/enttec-usb-dmx-pro')); 18 | this.registerDriver('enttec-open-usb-dmx', require('./drivers/enttec-open-usb-dmx')); 19 | this.registerDriver('dmxking-ultra-dmx-pro', require('./drivers/dmxking-ultra-dmx-pro')); 20 | this.registerDriver('artnet', require('./drivers/artnet')); 21 | this.registerDriver('bbdmx', require('./drivers/bbdmx')); 22 | this.registerDriver('sacn', require('./drivers/sacn')); 23 | } 24 | 25 | registerDriver(name, module) { 26 | this.drivers[name] = module; 27 | } 28 | 29 | addUniverse(name, driver, deviceId, options) { 30 | this.universes[name] = new this.drivers[driver](deviceId, options); 31 | 32 | this.universes[name].on('update', (channels, extraData) => { 33 | this.emit('update', name, channels, extraData); 34 | }); 35 | 36 | return this.universes[name]; 37 | } 38 | 39 | update(universe, channels, extraData) { 40 | this.universes[universe].update(channels, extraData || {}); 41 | } 42 | 43 | updateAll(universe, value) { 44 | this.universes[universe].updateAll(value); 45 | this.emit('updateAll', universe, value); 46 | } 47 | 48 | universeToObject(universeKey) { 49 | const universe = this.universes[universeKey]; 50 | const u = {}; 51 | 52 | for (let i = 0; i < 512; i++) { 53 | u[i] = universe.get(i); 54 | } 55 | return u; 56 | } 57 | } 58 | 59 | util.inherits(DMX, EventEmitter); 60 | 61 | DMX.devices = require('./devices'); 62 | DMX.Animation = require('./anim'); 63 | 64 | module.exports = DMX; 65 | -------------------------------------------------------------------------------- /drivers/enttec-open-usb-dmx.js: -------------------------------------------------------------------------------- 1 | const { SerialPort } = require('serialport'); 2 | const util = require('util'); 3 | const EventEmitter = require('events').EventEmitter; 4 | 5 | function EnttecOpenUsbDMX(deviceId, options = {}) { 6 | this.universe = Buffer.alloc(513); 7 | this.readyToWrite = true; 8 | this.interval = options.dmx_speed ? (1000 / options.dmx_speed) : 46; 9 | 10 | this.dev = new SerialPort({ 11 | 'path': deviceId, 12 | 'baudRate': 250000, 13 | 'dataBits': 8, 14 | 'stopBits': 2, 15 | 'parity': 'none', 16 | }, err => { 17 | if (!err) { 18 | this.start(); 19 | } else { 20 | console.warn(err); 21 | } 22 | }); 23 | } 24 | 25 | EnttecOpenUsbDMX.prototype.sendUniverse = function () { 26 | const self = this; 27 | 28 | if (!this.dev.writable) { 29 | return; 30 | } 31 | 32 | // toggle break 33 | self.dev.set({brk: true, rts: true}, (err, r) => { 34 | setTimeout(() => { 35 | self.dev.set({brk: false, rts: true}, (err, r) => { 36 | setTimeout(() => { 37 | if (self.readyToWrite) { 38 | self.readyToWrite = false; 39 | self.dev.write(Buffer.concat([Buffer([0]), self.universe.slice(1)])); 40 | self.dev.drain(() => { 41 | self.readyToWrite = true; 42 | }); 43 | } 44 | }, 1); 45 | }); 46 | }, 1); 47 | }); 48 | }; 49 | 50 | EnttecOpenUsbDMX.prototype.start = function () { 51 | this.intervalhandle = setInterval(this.sendUniverse.bind(this), this.interval); 52 | }; 53 | 54 | EnttecOpenUsbDMX.prototype.stop = function () { 55 | clearInterval(this.intervalhandle); 56 | }; 57 | 58 | EnttecOpenUsbDMX.prototype.close = function (cb) { 59 | this.stop(); 60 | this.dev.close(cb); 61 | }; 62 | 63 | EnttecOpenUsbDMX.prototype.update = function (u, extraData) { 64 | for (const c in u) { 65 | this.universe[c] = u[c]; 66 | } 67 | 68 | this.emit('update', u, extraData); 69 | }; 70 | 71 | EnttecOpenUsbDMX.prototype.updateAll = function (v) { 72 | for (let i = 1; i <= 512; i++) { 73 | this.universe[i] = v; 74 | } 75 | }; 76 | 77 | EnttecOpenUsbDMX.prototype.get = function (c) { 78 | return this.universe[c]; 79 | }; 80 | 81 | util.inherits(EnttecOpenUsbDMX, EventEmitter); 82 | 83 | module.exports = EnttecOpenUsbDMX; 84 | -------------------------------------------------------------------------------- /drivers/artnet.js: -------------------------------------------------------------------------------- 1 | const dgram = require('dgram'); 2 | const util = require('util'); 3 | const EventEmitter = require('events').EventEmitter; 4 | 5 | function ArtnetDriver(deviceId = '127.0.0.1', options = {}) { 6 | this.readyToWrite = true; 7 | this.header = Buffer.from([65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14]); 8 | this.sequence = Buffer.from([0]); 9 | this.physical = Buffer.from([0]); 10 | this.universeId = Buffer.from([0x00, 0x00]); 11 | this.length = Buffer.from([0x02, 0x00]); 12 | 13 | this.universe = Buffer.alloc(513); 14 | this.universe.fill(0); 15 | 16 | /** 17 | * Allow artnet rate to be set and default to 44Hz 18 | * @type Number 19 | */ 20 | this.interval = !isNaN(options.dmx_speed) ? 1000 / options.dmx_speed : 24; 21 | 22 | this.universeId.writeInt16LE(options.universe || 0, 0); 23 | this.host = deviceId; 24 | this.port = options.port || 6454; 25 | this.dev = dgram.createSocket('udp4'); 26 | this.dev.bind(() => this.dev.setBroadcast(true)); 27 | this.start(); 28 | } 29 | 30 | ArtnetDriver.prototype.sendUniverse = function (_) { 31 | const pkg = Buffer.concat([ 32 | this.header, 33 | this.sequence, 34 | this.physical, 35 | this.universeId, 36 | this.length, 37 | this.universe.slice(1), 38 | ]); 39 | 40 | if (this.readyToWrite) { 41 | this.readyToWrite = false; 42 | this.dev.send(pkg, 0, pkg.length, this.port, this.host, () => { 43 | this.readyToWrite = true; 44 | }); 45 | } 46 | }; 47 | 48 | ArtnetDriver.prototype.start = function () { 49 | this.timeout = setInterval(this.sendUniverse.bind(this), this.interval); 50 | }; 51 | 52 | ArtnetDriver.prototype.stop = function () { 53 | clearInterval(this.timeout); 54 | }; 55 | 56 | ArtnetDriver.prototype.close = function (cb) { 57 | this.stop(); 58 | cb(null); 59 | }; 60 | 61 | ArtnetDriver.prototype.update = function (u, extraData) { 62 | for (const c in u) { 63 | this.universe[c] = u[c]; 64 | } 65 | 66 | this.emit('update', u, extraData); 67 | }; 68 | 69 | ArtnetDriver.prototype.updateAll = function (v, _) { 70 | for (let i = 1; i <= 512; i++) { 71 | this.universe[i] = v; 72 | } 73 | }; 74 | 75 | ArtnetDriver.prototype.get = function (c) { 76 | return this.universe[c]; 77 | }; 78 | 79 | util.inherits(ArtnetDriver, EventEmitter); 80 | 81 | module.exports = ArtnetDriver; 82 | -------------------------------------------------------------------------------- /drivers/enttec-usb-dmx-pro.js: -------------------------------------------------------------------------------- 1 | const { SerialPort } = require('serialport'); 2 | const util = require('util'); 3 | const EventEmitter = require('events').EventEmitter; 4 | 5 | const ENTTEC_PRO_DMX_STARTCODE = 0x00; 6 | const ENTTEC_PRO_START_OF_MSG = 0x7e; 7 | const ENTTEC_PRO_END_OF_MSG = 0xe7; 8 | const ENTTEC_PRO_SEND_DMX_RQ = 0x06; 9 | // var ENTTEC_PRO_RECV_DMX_PKT = 0x05; 10 | 11 | function EnttecUSBDMXPRO(deviceId, options = {}) { 12 | this.universe = Buffer.alloc(513, 0); 13 | this.readyToWrite = true; 14 | this.interval = 1000 / (options.dmx_speed || 40); 15 | 16 | this.dev = new SerialPort({ 17 | 'path': deviceId, 18 | 'baudRate': 250000, 19 | 'dataBits': 8, 20 | 'stopBits': 2, 21 | 'parity': 'none', 22 | }, err => { 23 | if (!err) { 24 | this.start(); 25 | } else { 26 | console.warn(err); 27 | } 28 | }); 29 | } 30 | 31 | EnttecUSBDMXPRO.prototype.sendUniverse = function () { 32 | if (!this.dev.writable) { 33 | return; 34 | } 35 | 36 | if (this.readyToWrite) { 37 | const hdr = Buffer.from([ 38 | ENTTEC_PRO_START_OF_MSG, 39 | ENTTEC_PRO_SEND_DMX_RQ, 40 | (this.universe.length) & 0xff, 41 | ((this.universe.length) >> 8) & 0xff, 42 | ENTTEC_PRO_DMX_STARTCODE, 43 | ]); 44 | 45 | const msg = Buffer.concat([ 46 | hdr, 47 | this.universe.slice(1), 48 | Buffer.from([ENTTEC_PRO_END_OF_MSG]), 49 | ]); 50 | 51 | this.readyToWrite = false; 52 | this.dev.write(msg); 53 | this.dev.drain(() => { 54 | this.readyToWrite = true; 55 | }); 56 | } 57 | }; 58 | 59 | EnttecUSBDMXPRO.prototype.start = function () { 60 | this.intervalhandle = setInterval(this.sendUniverse.bind(this), this.interval); 61 | }; 62 | 63 | EnttecUSBDMXPRO.prototype.stop = function () { 64 | clearInterval(this.intervalhandle); 65 | }; 66 | 67 | EnttecUSBDMXPRO.prototype.close = function (cb) { 68 | this.dev.close(cb); 69 | }; 70 | 71 | EnttecUSBDMXPRO.prototype.update = function (u, extraData) { 72 | for (const c in u) { 73 | this.universe[c] = u[c]; 74 | } 75 | 76 | this.emit('update', u, extraData); 77 | }; 78 | 79 | EnttecUSBDMXPRO.prototype.updateAll = function (v) { 80 | for (let i = 1; i <= 512; i++) { 81 | this.universe[i] = v; 82 | } 83 | }; 84 | 85 | EnttecUSBDMXPRO.prototype.get = function (c) { 86 | return this.universe[c]; 87 | }; 88 | 89 | util.inherits(EnttecUSBDMXPRO, EventEmitter); 90 | 91 | module.exports = EnttecUSBDMXPRO; 92 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | const DMX = require('./index'); 2 | 3 | const dmx = new DMX(); 4 | const A = dmx.animation; 5 | 6 | // var universe = dmx.addUniverse('demo', 'enttec-usb-dmx-pro', '/dev/cu.usbserial-6AVNHXS8') 7 | // var universe = dmx.addUniverse('demo', 'enttec-open-usb-dmx', '/dev/cu.usbserial-6AVNHXS8') 8 | // const universe = dmx.addUniverse('demo', 'socketio', null, {port: 17809, debug: true}); 9 | const universe = dmx.addUniverse('demo', 'null'); 10 | 11 | universe.update({1: 1, 2: 0}); 12 | universe.update({16: 1, 17: 255}); 13 | universe.update({1: 255, 3: 120, 4: 230, 5: 30, 6: 110, 7: 255, 8: 10, 9: 255, 10: 255, 11: 0}); 14 | 15 | function greenWater(universe, channels, duration) { 16 | const colors = [ 17 | [160, 230, 20], 18 | [255, 255, 0], 19 | [110, 255, 10], 20 | ]; 21 | 22 | for (const c in channels) { 23 | const r = Math.floor((Math.random() * colors.length)); 24 | const u = {}; 25 | 26 | for (let i = 0; i < 3; i++) { 27 | u[channels[c] + i] = colors[r][i]; 28 | } 29 | new A().add(u, duration).run(universe); 30 | } 31 | setTimeout(function () {greenWater(universe, channels, duration);}, duration * 2); 32 | } 33 | 34 | function warp(universe, channel, min, max, duration) { 35 | const a = {}, b = {}; 36 | 37 | a[channel] = min; 38 | b[channel] = max; 39 | new A().add(a, duration).add(b, duration).run(universe, function () { 40 | warp(universe, channel, min, max, duration); 41 | }); 42 | } 43 | 44 | warp(universe, 1, 200, 220, 360); 45 | warp(universe, 1 + 15, 200, 255, 240); 46 | greenWater(universe, [3, 6, 9], 4000); 47 | greenWater(universe, [3 + 15, 6 + 15, 9 + 15], 4000); 48 | 49 | // function done() { console.log('DONE'); } 50 | // 51 | // const x = new A() 52 | // .add({1: 255, 6: 110, 7: 255, 8: 10}, 1200) 53 | // .delay(1000) 54 | // .add({1: 0}, 600) 55 | // .add({1: 255}, 600) 56 | // .add({5: 255, 6: 128}, 1000) 57 | // .add({1: 0}, 100) 58 | // .add({1: 255}, 100) 59 | // .add({1: 0}, 200) 60 | // .add({1: 255}, 200) 61 | // .add({1: 0}, 100) 62 | // .add({1: 255}, 100) 63 | // .add({1: 0}) 64 | // .delay(50) 65 | // .add({1: 255}) 66 | // .delay(50) 67 | // .add({1: 0}) 68 | // .delay(50) 69 | // .add({1: 255}) 70 | // .delay(50) 71 | // .add({1: 0}) 72 | // .delay(50) 73 | // .add({1: 255}) 74 | // .delay(50) 75 | // .add({2: 255}, 6000) 76 | // .delay(200) 77 | // .add({2: 0}); 78 | 79 | // const y = new A() 80 | // .add({9: 255}, 10000); 81 | 82 | // x.run(universe, done); 83 | // y.run(universe, done); 84 | -------------------------------------------------------------------------------- /drivers/dmxking-ultra-dmx-pro.js: -------------------------------------------------------------------------------- 1 | const { SerialPort } = require('serialport'); 2 | const util = require('util'); 3 | const EventEmitter = require('events').EventEmitter; 4 | 5 | const DMXKING_ULTRA_DMX_PRO_DMX_STARTCODE = 0x00; 6 | const DMXKING_ULTRA_DMX_PRO_START_OF_MSG = 0x7e; 7 | const DMXKING_ULTRA_DMX_PRO_END_OF_MSG = 0xe7; 8 | const DMXKING_ULTRA_DMX_PRO_SEND_DMX_RQ = 0x06; 9 | const DMXKING_ULTRA_DMX_PRO_SEND_DMX_A_RQ = 0x64; 10 | const DMXKING_ULTRA_DMX_PRO_SEND_DMX_B_RQ = 0x65; 11 | // var DMXKING_ULTRA_DMX_PRO_RECV_DMX_PKT = 0x05; 12 | 13 | function DMXKingUltraDMXPro(deviceId, options = {}) { 14 | this.options = options; 15 | this.universe = Buffer.alloc(513, 0); 16 | this.readyToWrite = true; 17 | this.interval = 1000 / (options.dmx_speed || 40); 18 | 19 | this.sendDMXReq = DMXKING_ULTRA_DMX_PRO_SEND_DMX_RQ; 20 | if (this.options.port === 'A') { 21 | this.sendDMXReq = DMXKING_ULTRA_DMX_PRO_SEND_DMX_A_RQ; 22 | } else if (this.options.port === 'B') { 23 | this.sendDMXReq = DMXKING_ULTRA_DMX_PRO_SEND_DMX_B_RQ; 24 | } 25 | 26 | this.dev = new SerialPort({ 27 | 'path': deviceId, 28 | 'baudRate': 250000, 29 | 'dataBits': 8, 30 | 'stopBits': 2, 31 | 'parity': 'none', 32 | }, err => { 33 | if (!err) { 34 | this.start(); 35 | } else { 36 | console.warn(err); 37 | } 38 | }); 39 | } 40 | 41 | DMXKingUltraDMXPro.prototype.sendUniverse = function () { 42 | if (!this.dev.writable) { 43 | return; 44 | } 45 | 46 | if (this.readyToWrite) { 47 | this.readyToWrite = false; 48 | 49 | const hdr = Buffer.from([ 50 | DMXKING_ULTRA_DMX_PRO_START_OF_MSG, 51 | this.sendDMXReq, 52 | (this.universe.length) & 0xff, 53 | ((this.universe.length) >> 8) & 0xff, 54 | DMXKING_ULTRA_DMX_PRO_DMX_STARTCODE, 55 | ]); 56 | 57 | const msg = Buffer.concat([ 58 | hdr, 59 | this.universe.slice(1), 60 | Buffer.from([DMXKING_ULTRA_DMX_PRO_END_OF_MSG]), 61 | ]); 62 | 63 | this.dev.write(msg); 64 | this.dev.drain(() => { 65 | this.readyToWrite = true; 66 | }); 67 | } 68 | }; 69 | 70 | DMXKingUltraDMXPro.prototype.start = function () { 71 | this.intervalhandle = setInterval(this.sendUniverse.bind(this), this.interval); 72 | }; 73 | 74 | DMXKingUltraDMXPro.prototype.stop = function () { 75 | clearInterval(this.intervalhandle); 76 | }; 77 | 78 | DMXKingUltraDMXPro.prototype.close = function (cb) { 79 | this.dev.close(cb); 80 | }; 81 | 82 | DMXKingUltraDMXPro.prototype.update = function (u, extraData) { 83 | for (const c in u) { 84 | this.universe[c] = u[c]; 85 | } 86 | 87 | this.emit('update', u, extraData); 88 | }; 89 | 90 | DMXKingUltraDMXPro.prototype.updateAll = function (v) { 91 | for (let i = 1; i <= 512; i++) { 92 | this.universe[i] = v; 93 | } 94 | }; 95 | 96 | DMXKingUltraDMXPro.prototype.get = function (c) { 97 | return this.universe[c]; 98 | }; 99 | 100 | util.inherits(DMXKingUltraDMXPro, EventEmitter); 101 | 102 | module.exports = DMXKingUltraDMXPro; 103 | -------------------------------------------------------------------------------- /anim.js: -------------------------------------------------------------------------------- 1 | const ease = require('./easing.js').ease; 2 | 3 | class Anim { 4 | constructor({ loop, filter } = {}) { 5 | this.frameDelay = 1; 6 | this.animations = []; 7 | this.lastAnimation = 0; 8 | this.timeout = null; 9 | this.duration = 0; 10 | this.startTime = null; 11 | this.loops = loop || 1; 12 | this.currentLoop = 0; 13 | this.filter = filter; 14 | } 15 | 16 | add(to, duration = 0, options = {}) { 17 | options.easing = options.easing || 'linear'; 18 | 19 | this.animations.push({ 20 | to, 21 | options, 22 | start: this.duration, 23 | end: this.duration + duration, 24 | }); 25 | this.duration += duration; 26 | 27 | return this; 28 | } 29 | 30 | delay(duration) { 31 | this.add({}, duration); 32 | return this; 33 | } 34 | 35 | stop() { 36 | if (this.timeout) { 37 | clearTimeout(this.timeout); 38 | } 39 | } 40 | 41 | reset(startTime = new Date().getTime()) { 42 | this.startTime = startTime; 43 | this.lastAnimation = 0; 44 | } 45 | 46 | runNextLoop(universe, onFinish) { 47 | const runAnimationStep = () => { 48 | const now = new Date().getTime(); 49 | const elapsedTime = now - this.startTime; 50 | 51 | this.timeout = setTimeout(runAnimationStep, this.frameDelay); 52 | 53 | // Find the animation for the current point in time, the latest if multiple match 54 | 55 | let currentAnimation = this.lastAnimation; 56 | 57 | while ( 58 | currentAnimation < this.animations.length && 59 | elapsedTime >= this.animations[currentAnimation].end 60 | ) { 61 | currentAnimation++; 62 | } 63 | 64 | // Ensure final state of all newly completed animations have been set 65 | const completedAnimations = this.animations.slice( 66 | this.lastAnimation, 67 | currentAnimation 68 | ); 69 | 70 | // Ensure future animations interpolate from the most recent state 71 | completedAnimations.forEach(completedAnimation => { 72 | delete completedAnimation.from; 73 | }); 74 | 75 | if (completedAnimations.length) { 76 | const completedAnimationStatesToSet = Object.assign( 77 | {}, 78 | ...completedAnimations.map(a => a.to) 79 | ); 80 | 81 | if (typeof this.filter === 'function') { 82 | this.filter(completedAnimationStatesToSet); 83 | } 84 | 85 | universe.update(completedAnimationStatesToSet, { origin: 'animation' }); 86 | } 87 | 88 | this.lastAnimation = currentAnimation; 89 | 90 | if (elapsedTime >= this.duration) { 91 | // This animation loop is complete 92 | this.currentLoop++; 93 | this.stop(); 94 | if (this.currentLoop >= this.loops) { 95 | // All loops complete 96 | if (onFinish) { 97 | onFinish(); 98 | } 99 | } else { 100 | // Run next loop 101 | this.reset(this.startTime + this.duration); 102 | this.runNextLoop(universe); 103 | } 104 | } else { 105 | // Set intermediate channel values during an animation 106 | const animation = this.animations[currentAnimation]; 107 | const easing = ease[animation.options.easing]; 108 | const duration = animation.end - animation.start; 109 | const animationElapsedTime = elapsedTime - animation.start; 110 | 111 | if (!animation.from) { 112 | animation.from = {}; 113 | for (const k in animation.to) { 114 | animation.from[k] = universe.get(k); 115 | } 116 | if (animation.options.from) { 117 | animation.from = Object.assign(animation.from, animation.options.from); 118 | } 119 | } 120 | 121 | if (duration) { 122 | const easeProgress = easing( 123 | Math.min(animationElapsedTime, duration), 124 | 0, 125 | 1, 126 | duration 127 | ); 128 | const intermediateValues = {}; 129 | 130 | for (const k in animation.to) { 131 | const startValue = animation.from[k]; 132 | const endValue = animation.to[k]; 133 | 134 | intermediateValues[k] = Math.round( 135 | startValue + easeProgress * (endValue - startValue) 136 | ); 137 | } 138 | 139 | if (typeof this.filter === 'function') { 140 | this.filter(intermediateValues); 141 | } 142 | 143 | universe.update(intermediateValues, { origin: 'animation' }); 144 | } 145 | } 146 | }; 147 | 148 | runAnimationStep(); 149 | 150 | return this; 151 | } 152 | 153 | run(universe, onFinish) { 154 | if (universe.interval) { 155 | // Optimisation to run animation updates at double the rate of driver updates using Nyquist's theorem 156 | this.frameDelay = universe.interval / 2; 157 | } 158 | this.reset(); 159 | this.currentLoop = 0; 160 | this.runNextLoop(universe, onFinish); 161 | } 162 | 163 | runLoop(universe, onFinish, loops = Infinity) { 164 | this.loops = loops; 165 | this.run(universe, onFinish); 166 | return this; 167 | } 168 | } 169 | 170 | module.exports = Anim; 171 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | 9 | "globals": { 10 | "document": false, 11 | "escape": false, 12 | "navigator": false, 13 | "unescape": false, 14 | "window": false, 15 | "describe": true, 16 | "before": true, 17 | "it": true, 18 | "expect": true, 19 | "sinon": true 20 | }, 21 | 22 | "parser": "babel-eslint", 23 | 24 | "plugins": [ 25 | 26 | ], 27 | 28 | "rules": { 29 | "block-scoped-var": 2, 30 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 31 | "camelcase": [2, { "properties": "always" }], 32 | "comma-dangle": [2, "always-multiline"], 33 | "comma-spacing": [2, { "before": false, "after": true }], 34 | "comma-style": [2, "last"], 35 | "complexity": 0, 36 | "consistent-return": 2, 37 | "consistent-this": 0, 38 | "curly": [2, "multi-line"], 39 | "default-case": 0, 40 | "dot-location": [2, "property"], 41 | "dot-notation": 0, 42 | "eol-last": 2, 43 | "eqeqeq": [2, "allow-null"], 44 | "func-names": 0, 45 | "func-style": 0, 46 | "generator-star-spacing": [2, "both"], 47 | "guard-for-in": 0, 48 | "handle-callback-err": [0, "^(err|error|anySpecificError)$" ], 49 | "indent": [2, 2, { "SwitchCase": 1 }], 50 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 51 | "keyword-spacing": [2, {"before": true, "after": true}], 52 | "linebreak-style": 0, 53 | "max-depth": 0, 54 | "max-len": [2, 120, 4], 55 | "max-nested-callbacks": 0, 56 | "max-params": 0, 57 | "max-statements": 0, 58 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 59 | "newline-after-var": [2, "always"], 60 | "new-parens": 2, 61 | "no-alert": 0, 62 | "no-array-constructor": 2, 63 | "no-bitwise": 0, 64 | "no-caller": 2, 65 | "no-catch-shadow": 0, 66 | "no-cond-assign": 2, 67 | "no-console": 0, 68 | "no-constant-condition": 0, 69 | "no-continue": 0, 70 | "no-control-regex": 2, 71 | "no-debugger": 2, 72 | "no-delete-var": 2, 73 | "no-div-regex": 0, 74 | "no-dupe-args": 2, 75 | "no-dupe-keys": 2, 76 | "no-duplicate-case": 2, 77 | "no-else-return": 2, 78 | "no-empty": 0, 79 | "no-empty-character-class": 2, 80 | "no-eq-null": 0, 81 | "no-eval": 2, 82 | "no-ex-assign": 2, 83 | "no-extend-native": 2, 84 | "no-extra-bind": 2, 85 | "no-extra-boolean-cast": 2, 86 | "no-extra-parens": 0, 87 | "no-extra-semi": 0, 88 | "no-extra-strict": 0, 89 | "no-fallthrough": 2, 90 | "no-floating-decimal": 2, 91 | "no-func-assign": 2, 92 | "no-implied-eval": 2, 93 | "no-inline-comments": 0, 94 | "no-inner-declarations": [2, "functions"], 95 | "no-invalid-regexp": 2, 96 | "no-irregular-whitespace": 2, 97 | "no-iterator": 2, 98 | "no-label-var": 2, 99 | "no-labels": 2, 100 | "no-lone-blocks": 0, 101 | "no-lonely-if": 0, 102 | "no-loop-func": 0, 103 | "no-mixed-requires": 0, 104 | "no-mixed-spaces-and-tabs": [2, false], 105 | "no-multi-spaces": 2, 106 | "no-multi-str": 2, 107 | "no-multiple-empty-lines": [2, { "max": 1 }], 108 | "no-native-reassign": 2, 109 | "no-negated-in-lhs": 2, 110 | "no-nested-ternary": 0, 111 | "no-new": 2, 112 | "no-new-func": 2, 113 | "no-new-object": 2, 114 | "no-new-require": 2, 115 | "no-new-wrappers": 2, 116 | "no-obj-calls": 2, 117 | "no-octal": 2, 118 | "no-octal-escape": 2, 119 | "no-path-concat": 0, 120 | "no-plusplus": 0, 121 | "no-process-env": 0, 122 | "no-process-exit": 0, 123 | "no-proto": 2, 124 | "no-redeclare": 2, 125 | "no-regex-spaces": 2, 126 | "no-reserved-keys": 0, 127 | "no-restricted-modules": 0, 128 | "no-return-assign": 2, 129 | "no-script-url": 0, 130 | "no-self-compare": 2, 131 | "no-sequences": 2, 132 | "no-shadow": 0, 133 | "no-shadow-restricted-names": 2, 134 | "no-spaced-func": 2, 135 | "no-sparse-arrays": 2, 136 | "no-sync": 0, 137 | "no-ternary": 0, 138 | "no-throw-literal": 2, 139 | "no-trailing-spaces": 2, 140 | "no-undef": 2, 141 | "no-undef-init": 2, 142 | "no-undefined": 0, 143 | "no-underscore-dangle": 0, 144 | "no-unneeded-ternary": 2, 145 | "no-unreachable": 1, 146 | "no-unused-expressions": 0, 147 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 148 | "no-use-before-define": 2, 149 | "no-var": 2, 150 | "no-void": 0, 151 | "no-warning-comments": 0, 152 | "no-with": 2, 153 | "one-var": 0, 154 | "operator-assignment": 0, 155 | "operator-linebreak": [2, "after"], 156 | "padded-blocks": 0, 157 | "prefer-const": 2, 158 | "quote-props": 0, 159 | "quotes": [2, "single", "avoid-escape"], 160 | "radix": 2, 161 | "semi": [2, "always"], 162 | "semi-spacing": 0, 163 | "sort-vars": 0, 164 | "space-before-blocks": [2, "always"], 165 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], 166 | "space-in-brackets": 0, 167 | "space-in-parens": [2, "never"], 168 | "space-infix-ops": 2, 169 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 170 | "spaced-comment": [2, "always"], 171 | "strict": 2, 172 | "use-isnan": 2, 173 | "valid-jsdoc": 0, 174 | "valid-typeof": 2, 175 | "vars-on-top": 0, 176 | "wrap-iife": [2, "any"], 177 | "wrap-regex": 0, 178 | "yoda": [2, "never"] 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # node-dmx 2 | 3 | DMX-512 controller library for node.js 4 | 5 | ## Install 6 | 7 | npm install dmx 8 | 9 | ## Library API 10 | ```javascript 11 | const DMX = require('dmx') 12 | ``` 13 | 14 | ### Class DMX 15 | 16 | #### new DMX() 17 | 18 | Create a new DMX instance. This class is used to tie multiple universes together. 19 | 20 | #### dmx.registerDriver(name, module) 21 | 22 | - name - String 23 | - module - Object implementing the Driver API 24 | 25 | 26 | Register a new DMX Driver module by its name. 27 | These drivers are currently registered by default: 28 | 29 | - null: a development driver that prints the universe to stdout 30 | - socketio: a driver which sends out the universe via socket.IO as an array (see [demo_socket_client.js](./demo_socket_client.js) as a client example) 31 | - artnet: driver for EnttecODE 32 | - bbdmx: driver for [BeagleBone-DMX](https://github.com/boxysean/beaglebone-DMX) 33 | - dmx4all: driver for DMX4ALL devices like the "NanoDMX USB Interface" 34 | - enttec-usb-dmx-pro: a driver for devices using a Enttec USB DMX Pro chip like the "DMXKing ultraDMX Micro". 35 | - enttec-open-usb-dmx: driver for "Enttec Open DMX USB". This device is NOT recommended, there are known hardware limitations and this driver is not very stable. (If possible better obtain a device with the "pro" chip) 36 | - dmxking-utra-dmx-pro: driver for the DMXKing Ultra DMX pro interface. This driver support multiple universe specify the options with Port = A or B 37 | 38 | #### dmx.addUniverse(name, driver, device_id, options) 39 | 40 | - name - String 41 | - driver - String, referring a registered driver 42 | - device_id - Number or Object 43 | - options - Object, driver specific options 44 | 45 | Add a new DMX Universe with a name, driver and an optional device_id used by the driver to identify the device. 46 | For enttec-usb-dmx-pro and enttec-open-usb-dmx device_id is the path the the serial device. For artnet it is the target ip. 47 | 48 | #### dmx.update(universe, channels[, extraData]) 49 | 50 | - universe - String, name of the universe 51 | - channels - Object, keys are channel numbers, values the values to set that channel to 52 | - extraData - Object, this data will be passed unmodified to the update Event. (Optional; default value is `{}`) 53 | 54 | Update one or multiple channels of a universe. Also emits a update Event with the same information. 55 | 56 | 57 | #### DMX.devices 58 | 59 | A JSON Object describing some Devices and how many channels they use. 60 | Currently not many devices are in there but more can be added to the devices.js file. Pull requests welcome ;-) 61 | 62 | The following Devices are known: 63 | 64 | - generic - a one channel dimmer 65 | - showtec-multidim2 - 4 channel dimmer with 4A per channel 66 | - eurolite-led-bar - Led bar with 3 RGB color segments and some programms 67 | - stairville-led-par-56 - RGB LED Par Can with some programms 68 | 69 | ### Class DMX.Animation 70 | 71 | #### new DMX.Animation([options]) 72 | 73 | Create a new DMX Animation instance. This can be chained similar to jQuery. 74 | 75 | The options Object takes the following keys: 76 | 77 | - loop - Number, the number of times this animation sequence will loop when run is invoked. This value is overridden if you invoke runLoop. 78 | - filter - Function, allows you to read or modify the values being set to each channel during each animation step. 79 | 80 | If you specify a filter function, it must take a single object parameter in which keys are channel numbers and values are the values to set those channels to. 81 | You may modify the values in the object to override the values in real-time, for example to scale channel brightness based on a master fader. 82 | 83 | #### animation.add(to, duration, options) 84 | 85 | - to - Object, keys are channel numbers, values the values to set that channel to 86 | - duration - Number, duration in ms 87 | - options - Object 88 | 89 | Add an animation Step. 90 | The options Object takes an easing key which allows to set a easing function from the following list: 91 | 92 | - linear (default) 93 | - inQuad 94 | - outQuad 95 | - inOutQuad 96 | - inCubic 97 | - outCubic 98 | - inOutCubic 99 | - inQuart 100 | - outQuart 101 | - inOutQuart 102 | - inQuint 103 | - outQuint 104 | - inOutQuint 105 | - inSine 106 | - outSine 107 | - inOutSine 108 | - inExpo 109 | - outExpo 110 | - inOutExpo 111 | - inCirc 112 | - outCirc 113 | - inOutCirc 114 | - inElastic 115 | - outElastic 116 | - inOutElastic 117 | - inBack 118 | - outBack 119 | - inOutBack 120 | - inBounce 121 | - outBounce 122 | - inOutBounce 123 | 124 | Returns a Animation object with the animation step added. 125 | 126 | 127 | #### animation.delay(duration) 128 | 129 | - duration - Number, duration in ms 130 | 131 | Delay the next animation step for duration. 132 | Returns a Animation object with the delay step added. 133 | 134 | 135 | #### animation.run(universe, onFinish) 136 | 137 | - universe - Object, reference to the universe driver 138 | - onFinish - Function, called when the animation is done 139 | 140 | Run the Animation on the specified universe. 141 | 142 | #### animation.runLoop(universe) 143 | 144 | - universe - Object, reference to the universe driver 145 | 146 | Runs an animation constantly until animation.stop() is called 147 | 148 | The example below shows a value being animated for 5 seconds: 149 | ```javascript 150 | const animation = new DMX.Animation().add({ 151 | 1: 255, 152 | }, 100).add({ 153 | 1: 0, 154 | }, 100).runLoop(universe) 155 | 156 | 157 | setTimeout(() => { 158 | animation.stop() 159 | }, 5000) 160 | ``` 161 | 162 | #### update Event 163 | 164 | - universe - String, name of the universe 165 | - channels - Object, keys are channel numbers, values the values to set that channel to 166 | - extraData - Object, data that was passed to the update method. 167 | 168 | This event is emitted whenever update is called either by the integrating application or by an animation step. 169 | 170 | If triggered by an animation step, extraData.origin will be the string 'animation'. 171 | 172 | ## Webinterface 173 | 174 | Versions prior to 0.2 included a Webinterface. This has since been moved into its own repository at 175 | 176 | ## Community 177 | 178 | We're happy to help. Chat with us on IRC in #dmx on libera.chat. 179 | -------------------------------------------------------------------------------- /easing.js: -------------------------------------------------------------------------------- 1 | /* 2 | * based on jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ 3 | * 4 | * TERMS OF USE - jQuery Easing 5 | * 6 | * Open source under the BSD License. 7 | * 8 | * Copyright © 2008 George McGinley Smith 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, 12 | * are permitted provided that the following conditions are met: 13 | * 14 | * Redistributions of source code must retain the above copyright notice, this list of 15 | * conditions and the following disclaimer. 16 | * Redistributions in binary form must reproduce the above copyright notice, this list 17 | * of conditions and the following disclaimer in the documentation and/or other materials 18 | * provided with the distribution. 19 | * 20 | * Neither the name of the author nor the names of contributors may be used to endorse 21 | * or promote products derived from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 24 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 25 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 28 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 31 | * OF THE POSSIBILITY OF SUCH DAMAGE. 32 | * 33 | */ 34 | 35 | // t: current time, b: begInnIng value, c: change In value, d: duration 36 | 37 | exports.ease = { 38 | linear(t, b, c, d) { return c * t / d + b; }, 39 | inQuad(t, b, c, d) { return c * (t /= d) * t + b; }, 40 | outQuad(t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, 41 | inOutQuad(t, b, c, d) { 42 | if ((t /= d / 2) < 1) return c / 2 * t * t + b; 43 | return -c / 2 * ((--t) * (t - 2) - 1) + b; 44 | }, 45 | inCubic(t, b, c, d) { return c * (t /= d) * t * t + b; }, 46 | outCubic(t, b, c, d) { return c * ((t = t / d - 1) * t * t + 1) + b; }, 47 | inOutCubic(t, b, c, d) { 48 | if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; 49 | return c / 2 * ((t -= 2) * t * t + 2) + b; 50 | }, 51 | inQuart(t, b, c, d) { 52 | return c * (t /= d) * t * t * t + b; 53 | }, 54 | outQuart(t, b, c, d) { 55 | return -c * ((t = t / d - 1) * t * t * t - 1) + b; 56 | }, 57 | inOutQuart(t, b, c, d) { 58 | if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; 59 | return -c / 2 * ((t -= 2) * t * t * t - 2) + b; 60 | }, 61 | inQuint(t, b, c, d) { 62 | return c * (t /= d) * t * t * t * t + b; 63 | }, 64 | outQuint(t, b, c, d) { 65 | return c * ((t = t / d - 1) * t * t * t * t + 1) + b; 66 | }, 67 | inOutQuint(t, b, c, d) { 68 | if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b; 69 | return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; 70 | }, 71 | inSine(t, b, c, d) { 72 | return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; 73 | }, 74 | outSine(t, b, c, d) { 75 | return c * Math.sin(t / d * (Math.PI / 2)) + b; 76 | }, 77 | inOutSine(t, b, c, d) { 78 | return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; 79 | }, 80 | inExpo(t, b, c, d) { 81 | return (t === 0) ? b : c * (2 ** (10 * (t / d - 1))) + b; 82 | }, 83 | outExpo(t, b, c, d) { 84 | return (t === d) ? b + c : c * (-(2 ** (-10 * t / d)) + 1) + b; 85 | }, 86 | inOutExpo(t, b, c, d) { 87 | if (t === 0) return b; 88 | if (t === d) return b + c; 89 | if ((t /= d / 2) < 1) return c / 2 * (2 ** (10 * (t - 1))) + b; 90 | return c / 2 * (-(2 ** (-10 * --t)) + 2) + b; 91 | }, 92 | inCirc(t, b, c, d) { 93 | return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; 94 | }, 95 | outCirc(t, b, c, d) { 96 | return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; 97 | }, 98 | inOutCirc(t, b, c, d) { 99 | if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; 100 | return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; 101 | }, 102 | inElastic(t, b, c, d) { 103 | let s = 1.70158; let p = 0; let a = c; 104 | 105 | if (t === 0) return b; if ((t /= d) === 1) return b + c; if (!p) p = d * 0.3; 106 | if (a < Math.abs(c)) { a = c; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(c / a); 107 | 108 | return -(a * (2 ** (10 * (t -= 1))) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; 109 | }, 110 | outElastic(t, b, c, d) { 111 | let s = 1.70158; let p = 0; let a = c; 112 | 113 | if (t === 0) return b; if ((t /= d) === 1) return b + c; if (!p) p = d * 0.3; 114 | if (a < Math.abs(c)) { a = c; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(c / a); 115 | 116 | return a * (2 ** (-10 * t)) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; 117 | }, 118 | inOutElastic(t, b, c, d) { 119 | let s = 1.70158; let p = 0; let a = c; 120 | 121 | if (t === 0) return b; if ((t /= d / 2) === 2) return b + c; if (!p) p = d * (0.3 * 1.5); 122 | if (a < Math.abs(c)) { a = c; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(c / a); 123 | 124 | if (t < 1) return -0.5 * (a * (2 ** (10 * (t -= 1))) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; 125 | return a * (2 ** (-10 * (t -= 1))) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; 126 | }, 127 | inBack(t, b, c, d, s) { 128 | if (s === undefined) s = 1.70158; 129 | return c * (t /= d) * t * ((s + 1) * t - s) + b; 130 | }, 131 | outBack(t, b, c, d, s) { 132 | if (s === undefined) s = 1.70158; 133 | return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; 134 | }, 135 | inOutBack(t, b, c, d, s) { 136 | if (s === undefined) s = 1.70158; 137 | if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; 138 | return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; 139 | }, 140 | inBounce(t, b, c, d) { 141 | return c - exports.ease.outBounce(d - t, 0, c, d) + b; 142 | }, 143 | outBounce(t, b, c, d) { 144 | if ((t /= d) < (1 / 2.75)) { 145 | return c * (7.5625 * t * t) + b; 146 | } else if (t < (2 / 2.75)) { 147 | return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; 148 | } else if (t < (2.5 / 2.75)) { 149 | return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; 150 | } 151 | return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; 152 | 153 | }, 154 | inOutBounce(t, b, c, d) { 155 | if (t < d / 2) return exports.ease.inBounce(t * 2, 0, c, d) * 0.5 + b; 156 | return exports.ease.outBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; 157 | }, 158 | }; 159 | 160 | /* 161 | * 162 | * TERMS OF USE - EASING EQUATIONS 163 | * 164 | * Open source under the BSD License. 165 | * 166 | * Copyright © 2001 Robert Penner 167 | * All rights reserved. 168 | * 169 | * Redistribution and use in source and binary forms, with or without modification, 170 | * are permitted provided that the following conditions are met: 171 | * 172 | * Redistributions of source code must retain the above copyright notice, this list of 173 | * conditions and the following disclaimer. 174 | * Redistributions in binary form must reproduce the above copyright notice, this list 175 | * of conditions and the following disclaimer in the documentation and/or other materials 176 | * provided with the distribution. 177 | * 178 | * Neither the name of the author nor the names of contributors may be used to endorse 179 | * or promote products derived from this software without specific prior written permission. 180 | * 181 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 182 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 183 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 184 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 185 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 186 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 187 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 188 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 189 | * OF THE POSSIBILITY OF SUCH DAMAGE. 190 | * 191 | */ 192 | -------------------------------------------------------------------------------- /devices.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'generic': { 3 | channels: ['dimmer'], 4 | }, 5 | 'generic-rgb': { 6 | channels: ['red', 'green', 'blue'], 7 | }, 8 | 'showtec-multidim2': { 9 | channels: ['1', '2', '3', '4'], 10 | }, 11 | 'eurolite-led-bar': { 12 | channels: [ 13 | 'ctrl', 14 | 'dimmer', 15 | 'strobe', 16 | 'red0', 17 | 'green0', 18 | 'blue0', 19 | 'red1', 20 | 'green1', 21 | 'blue1', 22 | 'red2', 23 | 'green2', 24 | 'blue2', 25 | ], 26 | ranges: { 27 | 'ctrl': { 28 | 'type': 'option', 29 | 'options': [ 30 | { 'value': 0, 'label': 'Black Out' }, 31 | { 'value': 1, 'label': 'Dimmer 1' }, 32 | { 'value': 16, 'label': 'Dimmer 2' }, 33 | { 'value': 32, 'label': 'Red' }, 34 | { 'value': 48, 'label': 'Green' }, 35 | { 'value': 64, 'label': 'Blue' }, 36 | { 'value': 80, 'label': 'Purple' }, 37 | { 'value': 96, 'label': 'Yellow' }, 38 | { 'value': 112, 'label': 'Cyan' }, 39 | { 'value': 128, 'label': 'White' }, 40 | { 'value': 144, 'label': 'Color change' }, 41 | { 'value': 160, 'label': 'Color flow' }, 42 | { 'value': 176, 'label': 'Color dream' }, 43 | { 'value': 192, 'label': 'Multi flow' }, 44 | { 'value': 208, 'label': 'Dream flow' }, 45 | { 'value': 224, 'label': 'Two color flow' }, 46 | { 'value': 240, 'label': 'Sound activity' }, 47 | ], 48 | }, 49 | 'dimmer': { 50 | 'type': 'slider', 51 | 'min': 0, 52 | 'max': 255, 53 | }, 54 | }, 55 | }, 56 | 'stairville-led-par-56': { 57 | channels: ['ctrl', 'red', 'green', 'blue', 'speed'], 58 | ranges: { 59 | 'ctrl': { 60 | 'type': 'option', 61 | 'options': [ 62 | { 'value': 0, 'label': 'RGB Control' }, 63 | { 'value': 64, 'label': '7 color fade' }, 64 | { 'value': 128, 'label': '7 color change' }, 65 | { 'value': 192, 'label': '3 color change' }, 66 | ], 67 | }, 68 | }, 69 | }, 70 | 'ultra-pro-24ch-rdm': { 71 | channels: [...Array(25).keys()].slice(1), 72 | }, 73 | 'ultra-pro-6rgbch-rdm': { 74 | channels: [...Array(25).keys()].slice(1), 75 | channelgroups: ['1', '2', '3', '4', '5', '6'], 76 | }, 77 | 'oppsk-cob-uv-par': { 78 | channels: ['dimmer', 'strobe', 'program-speed', 'sound-activity'], 79 | }, 80 | 'lixda-par12-led': { 81 | channels: ['ctrl', 'static-color', 'speed', 'dimmer', 'red', 'green', 'blue', 'white'], 82 | ranges: { 83 | 'ctrl': { 84 | 'type': 'option', 85 | 'options': [ 86 | { 'value': 0, 'label': 'Off' }, 87 | { 'value': 11, 'label': 'Static Color' }, 88 | { 'value': 51, 'label': 'Jump' }, 89 | { 'value': 101, 'label': 'Gradual' }, 90 | { 'value': 151, 'label': 'Sound Activate' }, 91 | { 'value': 200, 'label': 'Strobe' }, 92 | ], 93 | }, 94 | 'static-color': { 95 | 'type': 'option', 96 | 'options': [ 97 | { 'value': 0, 'label': 'All Color' }, 98 | { 'value': 40, 'label': 'Red' }, 99 | { 'value': 50, 'label': 'Green' }, 100 | { 'value': 60, 'label': 'Blue' }, 101 | { 'value': 70, 'label': 'Yellow' }, 102 | { 'value': 80, 'label': 'Cyan' }, 103 | { 'value': 90, 'label': 'Purple' }, 104 | { 'value': 100, 'label': 'White' }, 105 | { 'value': 110, 'label': 'Red + Green' }, 106 | { 'value': 120, 'label': 'Red + Blue' }, 107 | { 'value': 130, 'label': 'Red + White' }, 108 | { 'value': 140, 'label': 'Green + Blue' }, 109 | { 'value': 150, 'label': 'Green + White' }, 110 | { 'value': 160, 'label': 'Blue + White' }, 111 | { 'value': 170, 'label': 'Red + Green + White' }, 112 | { 'value': 180, 'label': 'Red + Blue + White' }, 113 | { 'value': 190, 'label': 'Green + Blue + White' }, 114 | { 'value': 200, 'label': 'Red + Green + Blue' }, 115 | { 'value': 210, 'label': 'Red + Green + Blue + White' }, 116 | ], 117 | }, 118 | }, 119 | }, 120 | 'lixada-par12-led-v2': { 121 | channels: ['dimmer', 'red', 'green', 'blue', 'white', 'strobe', 'ctrl', 'speed'], 122 | ranges: { 123 | 'ctrl': { 124 | 'type': 'option', 125 | 'options': [ 126 | { 'value': 0, 'label': 'Off' }, 127 | { 'value': 11, 'label': 'Static Color' }, 128 | { 'value': 61, 'label': 'Gradient slow' }, 129 | { 'value': 85, 'label': 'Gradient medium' }, 130 | { 'value': 110, 'label': 'Gradient fast' }, 131 | { 'value': 111, 'label': 'Pulse slow' }, 132 | { 'value': 135, 'label': 'Pulse medium' }, 133 | { 'value': 160, 'label': 'Pulse fast' }, 134 | { 'value': 161, 'label': 'Jump slow' }, 135 | { 'value': 185, 'label': 'Jump medium' }, 136 | { 'value': 210, 'label': 'Jump fast' }, 137 | { 'value': 211, 'label': 'Sound Activate' }, 138 | ], 139 | }, 140 | }, 141 | }, 142 | 'lixada-par12-led-v3': { 143 | channels: ['dimmer', 'strobe', 'ctrl', 'speed', 'red', 'green', 'blue', 'white'], 144 | ranges: { 145 | 'ctrl': { 146 | 'type': 'option', 147 | 'options': [ 148 | { 'value': 0, 'label': 'Manual' }, 149 | { 'value': 51, 'label': 'A' }, 150 | { 'value': 101, 'label': 'Blinky RGBW' }, 151 | { 'value': 151, 'label': 'Fade A' }, 152 | { 'value': 201, 'label': 'Fade B' }, 153 | { 'value': 251, 'label': 'Off' }, 154 | ], 155 | }, 156 | }, 157 | }, 158 | 'eurolite-led-tha-120PC': { 159 | channels: ['red', 'green', 'blue', 'white', 'dimmer', 'strobe', 'effect'], 160 | }, 161 | 'briteq-bt-theatre-60FC': { 162 | channels: ['dimmer', 'strobe', 'effect', 'red', 'green', 'blue', 'white'], 163 | }, 164 | 'lalucenatz-led-4ch': { 165 | channels: ['master', 'red', 'green', 'blue'], 166 | }, 167 | 'fungeneration-led-pot-12x1w-qcl-rgbww-4ch': { 168 | channels: ['red', 'green', 'blue', 'white'], 169 | }, 170 | 'fungeneration-led-pot-12x1w-qcl-rgbww-6ch': { 171 | channels: ['dimmer', 'red', 'green', 'blue', 'white', 'strobe'], 172 | }, 173 | 'fungeneration-led-pot-12x1w-qcl-rgbww-8ch': { 174 | channels: ['dimmer', 'red', 'green', 'blue', 'white', 'programme-selection', 'colour-macros-programme-01', 'strobe'], 175 | ranges: { 176 | 'programme-selection': { 177 | 'type': 'option', 178 | 'options': [ 179 | { 'value': 0, 'label': 'No function' }, 180 | { 'value': 1, 'label': 'Programme 01' }, 181 | { 'value': 17, 'label': 'Programme 02' }, 182 | { 'value': 34, 'label': 'Programme 03' }, 183 | { 'value': 51, 'label': 'Programme 04' }, 184 | { 'value': 68, 'label': 'Programme 05' }, 185 | { 'value': 85, 'label': 'Programme 06' }, 186 | { 'value': 102, 'label': 'Programme 07' }, 187 | { 'value': 119, 'label': 'Programme 08' }, 188 | { 'value': 136, 'label': 'Programme 09' }, 189 | { 'value': 153, 'label': 'Programme 10' }, 190 | { 'value': 170, 'label': 'Programme 11' }, 191 | { 'value': 187, 'label': 'Programme 12' }, 192 | { 'value': 204, 'label': 'Programme 13' }, 193 | { 'value': 221, 'label': 'Programme 14' }, 194 | { 'value': 238, 'label': 'Programme Sound-controlled-operation' }, 195 | ], 196 | }, 197 | 'colour-macros-programme-01': { 198 | 'type': 'option', 199 | 'options': [ 200 | { 'value': 0, 'label': 'Red 0 % / Green 0 % / Blue 0 % / White 0 %, if channel 6 = 1 ... 16' }, 201 | { 'value': 16, 'label': 'Red 255 % / Green 0 % / Blue 0 % / White 0 %, if channel 6 = 1 ... 16' }, 202 | { 'value': 32, 'label': 'Red 0 % / Green 255 % / Blue 0 % / White 0 %, if channel 6 = 1 ... 16' }, 203 | { 'value': 48, 'label': 'Red 0 % / Green 0 % / Blue 255 % / White 0 %, if channel 6 = 1 ... 16' }, 204 | { 'value': 64, 'label': 'Red 0 % / Green 0 % / Blue 0 % / White 255 %, if channel 6 = 1 ... 16' }, 205 | { 'value': 80, 'label': 'Red 255 % / Green 150 % / Blue 0 % / White 0 %, if channel 6 = 1 ... 16' }, 206 | { 'value': 96, 'label': 'Red 255 % / Green 180 % / Blue 0 % / White 0 %, if channel 6 = 1 ... 16' }, 207 | { 'value': 112, 'label': 'Red 255 % / Green 255 % / Blue 0 % / White 0 %, if channel 6 = 1 ... 16' }, 208 | { 'value': 128, 'label': 'Red 255 % / Green 0 % / Blue 255 % / White 0 %, if channel 6 = 1 ... 16' }, 209 | { 'value': 144, 'label': 'Red 255 % / Green 0 % / Blue 140 % / White 0 %, if channel 6 = 1 ... 16' }, 210 | { 'value': 160, 'label': 'Red 0 % / Green 255 % / Blue 255 % / White 0 %, if channel 6 = 1 ... 16' }, 211 | { 'value': 176, 'label': 'Red 255 % / Green 0 % / Blue 0 % / White 210 %, if channel 6 = 1 ... 16' }, 212 | { 'value': 192, 'label': 'Red 0 % / Green 255 % / Blue 0 % / White 210 %, if channel 6 = 1 ... 16' }, 213 | { 'value': 208, 'label': 'Red 0 % / Green 0 % / Blue 255 % / White 210 %, if channel 6 = 1 ... 16' }, 214 | { 'value': 224, 'label': 'Red 255 % / Green 200 % / Blue 40 % / White 90 %, if channel 6 = 1 ... 16' }, 215 | { 'value': 240, 'label': 'Red 255 % / Green 255 % / Blue 255 % / White 255 %, if channel 6 = 1 ... 16' }, 216 | { 'value': 0, 'label': 'Microphone sensitivity, if channel 6 = 238 ... 255' }, 217 | ], 218 | }, 219 | 'strobe': { 220 | 'type': 'option', 221 | 'options': [ 222 | { 'value': 0, 'label': 'No function' }, 223 | { 'value': 1, 'label': 'Stroboscope effect (0 % to 100 %)' }, 224 | ], 225 | }, 226 | }, 227 | }, 228 | 'eurolite-led-bar-[6,12]-qcl-rgba-2ch': { 229 | channels: ['ctrl', 'DMX-Auto-program-speed,-increasing'], 230 | ranges: { 231 | 'ctrl': { 232 | 'type': 'option', 233 | 'options': [ 234 | { 'value': 0, 'label': 'No function' }, 235 | { 'value': 10, 'label': 'Auto mode via DMX' }, 236 | { 'value': 120, 'label': 'Sound mode via DMX' }, 237 | ], 238 | }, 239 | }, 240 | }, 241 | 'eurolite-led-bar-[6,12]-qcl-rgba-4ch': { 242 | channels: ['red', 'green', 'blue', 'amber'], 243 | }, 244 | 'eurolite-led-bar-[6,12]-qcl-rgba-5ch': { 245 | channels: ['dimmer', 'color-presets', 'strobe', 'ctrl', 'DMX-Auto-program-speed,-increasing'], 246 | ranges: { 247 | 'color-pretsets': { 248 | 'type': 'option', 249 | 'options': [ 250 | { 'value': 0, 'label': 'No function' }, 251 | { 'value': 10, 'label': 'Red' }, 252 | { 'value': 20, 'label': 'Green' }, 253 | { 'value': 30, 'label': 'Blue' }, 254 | { 'value': 40, 'label': 'Amber' }, 255 | { 'value': 50, 'label': 'Green Yellow' }, 256 | { 'value': 60, 'label': 'Cyan' }, 257 | { 'value': 70, 'label': 'Magenta' }, 258 | { 'value': 80, 'label': 'Yellow' }, 259 | { 'value': 90, 'label': 'Turquoise' }, 260 | { 'value': 100, 'label': 'Magenta' }, 261 | { 'value': 110, 'label': 'Orange' }, 262 | { 'value': 120, 'label': 'Light Green' }, 263 | { 'value': 130, 'label': 'Magenta' }, 264 | { 'value': 140, 'label': 'Pink' }, 265 | { 'value': 150, 'label': 'Light Green' }, 266 | { 'value': 160, 'label': 'Light Blue' }, 267 | { 'value': 170, 'label': 'Salmon' }, 268 | { 'value': 180, 'label': 'Cold white' }, 269 | { 'value': 190, 'label': 'Warm white' }, 270 | { 'value': 200, 'label': 'Lavender' }, 271 | ], 272 | }, 273 | 'ctrl': { 274 | 'type': 'option', 275 | 'options': [ 276 | { 'value': 0, 'label': 'No function' }, 277 | { 'value': 10, 'label': 'Auto mode via DMX' }, 278 | { 'value': 120, 'label': 'Sound mode via DMX' }, 279 | ], 280 | }, 281 | }, 282 | }, 283 | 'eurolite-led-bar-[6,12]-qcl-rgba-6ch': { 284 | channels: ['red', 'green', 'blue', 'amber', 'dimmer', 'ctrl'], 285 | ranges: { 286 | 'ctrl': { 287 | 'type': 'option', 288 | 'options': [ 289 | { 'value': 0, 'label': 'No function' }, 290 | { 'value': 1, 'label': 'Sound control' }, 291 | { 'value': 6, 'label': 'No function' }, 292 | { 'value': 11, 'label': 'Sound mode via DMX' }, 293 | ], 294 | }, 295 | }, 296 | }, 297 | 'eurolite-led-bar-[6,12]-qcl-rgba-9ch': { 298 | channels: ['red', 'green', 'blue', 'amber', 'temperature', 'dimmer', 'strobe', 'ctrl', 'DMX-Auto-program-speed,-increasing'], 299 | ranges: { 300 | 'temperature': { 301 | 'type': 'option', 302 | 'options:': [ 303 | { 'value': 0, 'label': 'No function' }, 304 | { 'value': 10, 'label': '2700K' }, 305 | { 'value': 30, 'label': '3200K' }, 306 | { 'value': 50, 'label': '3400K' }, 307 | { 'value': 70, 'label': '4200K' }, 308 | { 'value': 90, 'label': '4900K' }, 309 | { 'value': 110, 'label': '5600K' }, 310 | { 'value': 130, 'label': '6000K' }, 311 | { 'value': 150, 'label': '6500K' }, 312 | { 'value': 170, 'label': '7500K' }, 313 | { 'value': 190, 'label': '8000K' }, 314 | ], 315 | }, 316 | 'strobe': { 317 | 'type': 'option', 318 | 'options': [ 319 | { 'value': 0, 'label': 'No function' }, 320 | { 'value': 10, 'label': 'strobe with increasing speed' }, 321 | ], 322 | }, 323 | 'ctrl': { 324 | 'type': 'option', 325 | 'options': [ 326 | { 'value': 0, 'label': 'No function' }, 327 | { 'value': 10, 'label': 'Auto mode via DMX' }, 328 | { 'value': 120, 'label': 'Sound mode via DMX' }, 329 | ], 330 | }, 331 | }, 332 | }, 333 | }; 334 | --------------------------------------------------------------------------------