homebridge-dmxlight-plugin
Version:
A Homebridge plugin for controlling lights via DMX.
415 lines • 17.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DmxController = void 0;
const dmx_ts_1 = require("dmx-ts");
const sacnUniverse_1 = require("./sacnUniverse");
const shuffle_array_1 = __importDefault(require("shuffle-array"));
class DmxController {
// Constructor
constructor(serialPort, ipAddress, universe, driverName, channelStart, channelCount, colorOrder, transitionEffect, transitionEffectDuration, log) {
this.dmxUniverseName = 'dmxLightUniverse';
this.updateInterval = 100;
this.driverName = '';
this.colorOrder = 'rgb';
this.log = log;
this.dmx = new dmx_ts_1.DMX();
this.driverName = driverName;
this.colorOrder = colorOrder;
if (serialPort !== '') {
// Configure Enttec Pro
this.dmx.addUniverse(this.dmxUniverseName, new dmx_ts_1.EnttecUSBDMXProDriver(serialPort))
.then(() => {
this.log.info('successfully added universe for Enttec Pro');
})
.catch((err) => {
this.log.error('error adding universe for Enttec Pro: ' + err);
});
// Create a dummy sacnUniverse to fulfill class requirement
this.sacnUniverse = new sacnUniverse_1.SacnUniverse('', 1, 1, 1, '', '', 0, log);
return;
}
// Initialize SACN Universe if necessary
this.sacnUniverse = new sacnUniverse_1.SacnUniverse(ipAddress, universe, channelStart, channelCount, colorOrder, transitionEffect, transitionEffectDuration, log);
}
setOn(hue, saturation, brightness) {
const rgb = this.HSVtoRGB(hue / 360, saturation / 100, brightness / 100);
if (this.colorOrder !== 'w') {
this.log.info('DMX On with Color: HSV=' + hue + '/' + saturation + '%/' + brightness + '%, RGB=' + rgb.r + '/' +
rgb.g + '/' + rgb.b + ' (Universe #' + this.sacnUniverse.universe + ', Channels #' + this.sacnUniverse.channelStart + '-' +
(this.sacnUniverse.channelStart + this.sacnUniverse.channelCount - 1) + ', transition=' + this.sacnUniverse.transitionEffect +
', duration=' + this.sacnUniverse.transitionEffectDuration + ')');
}
else {
this.log.info('DMX On at 255! (Universe #' + this.sacnUniverse.universe + ', Channels #' + this.sacnUniverse.channelStart + '-' +
(this.sacnUniverse.channelStart + this.sacnUniverse.channelCount - 1));
}
// Remap colors if necessary
const colors = this.mapColors(rgb.r, rgb.g, rgb.b, this.sacnUniverse.colorOrder);
// If colors are all off then we need to change them to all on
if (colors[0] + colors[1] + colors[2] === 0) {
colors[0] = 255;
colors[1] = 255;
colors[2] = 255;
}
switch (this.driverName) {
case 'enttec-usb-dmx-pro':
this.log.info('setting channels ' + this.sacnUniverse.channelStart + '-' + (this.sacnUniverse.channelStart + 2) + ' on');
if (this.sacnUniverse.colorOrder === 'w') {
// eslint-disable-next-line no-case-declarations
const channel = { [this.sacnUniverse.channelStart]: 255 };
this.dmx.update(this.dmxUniverseName, channel);
return;
}
// eslint-disable-next-line no-case-declarations
const channel = { [this.sacnUniverse.channelStart]: colors[0], [this.sacnUniverse.channelStart + 1]: colors[1],
[this.sacnUniverse.channelStart + 2]: colors[2] };
this.dmx.update(this.dmxUniverseName, channel);
break;
case 'sacn':
switch (this.sacnUniverse.transitionEffect) {
case 'gradient':
this.applyFadeInTransition(colors[0], colors[1], colors[2]);
break;
case 'random':
this.applyRandomTransition(colors[0], colors[1], colors[2]);
break;
case 'chase':
this.applyChaseTransition(colors[0], colors[1], colors[2]);
break;
default:
if (this.sacnUniverse.colorOrder === 'w') {
this.setSacnSingle(255);
}
else {
this.setSacnColor(colors[0], colors[1], colors[2]);
}
}
break;
}
}
setOff() {
this.log.info('DMX Off (Universe #' + this.sacnUniverse.universe + ', Channels #' + this.sacnUniverse.channelStart + '-' +
(this.sacnUniverse.channelStart + this.sacnUniverse.channelCount - 1) + ')');
switch (this.driverName) {
case 'enttec-usb-dmx-pro':
if (this.sacnUniverse.colorOrder === 'w') {
// eslint-disable-next-line no-case-declarations
const channel = { [this.sacnUniverse.channelStart]: 0 };
this.dmx.update(this.dmxUniverseName, channel);
return;
}
// eslint-disable-next-line no-case-declarations
const channel = { [this.sacnUniverse.channelStart]: 0, [this.sacnUniverse.channelStart + 1]: 0,
[this.sacnUniverse.channelStart + 2]: 0 };
this.dmx.update(this.dmxUniverseName, channel);
break;
case 'sacn':
switch (this.sacnUniverse.transitionEffect) {
case 'gradient':
this.applyFadeOutTransition(0, 0, 0);
break;
case 'random':
this.applyRandomTransition(0, 0, 0);
break;
case 'chase':
this.applyChaseTransition(0, 0, 0);
break;
default:
if (this.sacnUniverse.colorOrder === 'w') {
this.setSacnSingle(0);
}
else {
this.setSacnColor(0, 0, 0);
}
}
break;
}
}
setHSB(hue, saturation, brightness) {
const rgb = this.HSVtoRGB(hue / 360, saturation / 100, brightness / 100);
if (this.colorOrder !== 'w') {
this.log.info('Set Color: HSV=' + hue + '/' + saturation + '%/' +
brightness + '%, RGB=' + rgb.r + '/' + rgb.g + '/' + rgb.b + ' (Universe #' + this.sacnUniverse.universe +
', Channels #' + this.sacnUniverse.channelStart + '-' + (this.sacnUniverse.channelStart +
this.sacnUniverse.channelCount - 1) + ')');
}
else {
this.log.info('Set Brightness to ' + (255 * brightness));
}
// Remap colors if necessary
const colors = this.mapColors(rgb.r, rgb.g, rgb.b, this.sacnUniverse.colorOrder);
switch (this.driverName) {
case 'enttec-usb-dmx-pro':
if (this.sacnUniverse.colorOrder === 'w') {
// eslint-disable-next-line no-case-declarations
const channel = { [this.sacnUniverse.channelStart]: 255 * brightness };
this.dmx.update(this.dmxUniverseName, channel);
return;
}
// eslint-disable-next-line no-case-declarations
let channel = { [this.sacnUniverse.channelStart]: colors[0] };
this.dmx.update(this.dmxUniverseName, channel);
channel = { [this.sacnUniverse.channelStart + 1]: colors[1] };
this.dmx.update(this.dmxUniverseName, channel);
channel = { [this.sacnUniverse.channelStart + 2]: colors[2] };
this.dmx.update(this.dmxUniverseName, channel);
break;
case 'sacn':
this.setSacnColor(colors[0], colors[1], colors[2]);
break;
}
}
setSacnColor(r, g, b) {
const endChannel = this.sacnUniverse.channelStart - 1 + this.sacnUniverse.channelCount - 1;
//this.log.info('Updating slots buffer from ' + (startChannel-1) + ' - ' + endChannel);
//this.log.info('Color set to ' + r + '/' + g + '/' + b);
let p = 1;
for (let idx = this.sacnUniverse.channelStart - 1; idx <= endChannel; idx++) {
switch (p) {
case 1:
this.sacnUniverse.sacnSlotsData[idx] = r;
break;
case 2:
this.sacnUniverse.sacnSlotsData[idx] = g;
break;
case 3:
this.sacnUniverse.sacnSlotsData[idx] = b;
break;
}
p++;
if (p > 3) {
p = 1;
}
}
this.sacnUniverse.sacnClient.send(this.sacnUniverse.sacnPacket);
}
// applyFadeOutTransition creates a smooth transition from the current color to off
applyFadeOutTransition(r, g, b) {
const ccRGB = this.getCurrentColor();
const ccHSV = this.rgbToHsv(ccRGB[0], ccRGB[1], ccRGB[2]);
// If its already at the target then bail
if (b === ccHSV[2]) {
return;
}
const fadeInterval = Math.round(this.sacnUniverse.transitionEffectDuration / this.updateInterval);
let brightness = ccHSV[2];
const brightnessDelta = Math.abs(brightness - b);
const stepAmount = brightnessDelta / fadeInterval;
const interval = fadeInterval;
const timerId = setInterval(() => {
brightness = brightness - stepAmount;
if (brightness < 0) {
brightness = 0;
}
const rgb = this.HSVtoRGB(ccHSV[0], ccHSV[1], brightness);
//console.log('h=' + ccHSV[0] + ', s=' + ccHSV[1] + ', v: ' + brightness + ' | r=' + rgb.r + ', g=' + rgb.g + ', b=' + rgb.b);
this.setSacnColor(rgb.r, rgb.g, rgb.b);
if (brightness <= 0) {
clearTimeout(timerId);
return;
}
}, interval);
}
// applyFadeInTransition creates a smooth transition from off to the desired color
applyFadeInTransition(r, g, b) {
const fadeInterval = Math.round(this.sacnUniverse.transitionEffectDuration / this.updateInterval);
const destColor = this.rgbToHsv(r, g, b);
let brightness = 0;
const brightnessDelta = destColor[2];
const stepAmount = brightnessDelta / fadeInterval;
const interval = fadeInterval;
const timerId = setInterval(() => {
brightness = brightness + stepAmount;
if (brightness > 1) {
brightness = 1;
}
const rgb = this.HSVtoRGB(destColor[0], destColor[1], brightness);
this.setSacnColor(rgb.r, rgb.g, rgb.b);
if (brightness >= 1) {
clearTimeout(timerId);
return;
}
}, interval);
}
// applyRandomTransition transitions each light to the desired color one at a time in a random order
applyRandomTransition(r, g, b) {
const switchOrder = this.createRandomColorSwitchOrder(this.sacnUniverse.channelCount);
const timeInterval = Math.round(this.sacnUniverse.transitionEffectDuration / switchOrder.length);
let index = 0;
const timerId = setInterval(() => {
this.setSacnColor(r, g, b);
index += 1;
if (index >= switchOrder.length) {
clearTimeout(timerId);
return;
}
}, timeInterval);
}
// applyChaseTransition transitions each light to the desired color one at a time from beginning to end
applyChaseTransition(r, g, b) {
const timeInterval = Math.round(this.sacnUniverse.transitionEffectDuration / (this.sacnUniverse.channelCount / 3));
let channelIndex = 0;
const timerId = setInterval(() => {
this.setSacnColor(r, g, b);
channelIndex += 3;
if (channelIndex >= this.sacnUniverse.channelCount) {
clearTimeout(timerId);
return;
}
}, timeInterval);
}
// createRandomColorSwitchOrder creates a random order of lights to change color
createRandomColorSwitchOrder(lightCount) {
const order = [];
// Create an array with the indexes for all lights. Since each light has three channels we need to skip two
for (let i = 1; i <= lightCount; i += 3) {
order.push(i);
}
// Shuffle the array randomly
shuffle_array_1.default(order);
return order;
}
HSVtoRGB(h, s, v) {
let r = 0;
let g = 0;
let b = 0;
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
r = v, g = t, b = p;
break;
case 1:
r = q, g = v, b = p;
break;
case 2:
r = p, g = v, b = t;
break;
case 3:
r = p, g = q, b = v;
break;
case 4:
r = t, g = p, b = v;
break;
case 5:
r = v, g = p, b = q;
break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
}
// mapColors maps standard rgb color order to something else
mapColors(r, g, b, colorOrder) {
const colors = [r, g, b];
if (colorOrder !== 'rgb') {
for (let i = 0; i < colorOrder.length; i++) {
switch (colorOrder[i]) {
case 'r':
colors[i] = r;
break;
case 'g':
colors[i] = g;
break;
case 'b':
colors[i] = b;
break;
}
}
}
return colors;
}
getCurrentColor() {
const firstChannel = this.sacnUniverse.sacnSlotsData[this.sacnUniverse.channelStart - 1];
const secondChannel = this.sacnUniverse.sacnSlotsData[this.sacnUniverse.channelStart];
const thirdChannel = this.sacnUniverse.sacnSlotsData[this.sacnUniverse.channelStart + 1];
let red = firstChannel;
let blue = secondChannel;
let green = thirdChannel;
switch (this.sacnUniverse.colorOrder.toLowerCase().substring(0, 1)) {
case 'r':
red = firstChannel;
break;
case 'g':
green = firstChannel;
break;
case 'b':
blue = firstChannel;
break;
}
switch (this.sacnUniverse.colorOrder.toLowerCase().substring(1, 2)) {
case 'r':
red = secondChannel;
break;
case 'g':
green = secondChannel;
break;
case 'b':
blue = secondChannel;
break;
}
switch (this.sacnUniverse.colorOrder.toLowerCase().substring(2, 3)) {
case 'r':
red = thirdChannel;
break;
case 'g':
green = thirdChannel;
break;
case 'b':
blue = thirdChannel;
break;
}
return [red, green, blue];
}
setSacnSingle(amount) {
const endChannel = this.sacnUniverse.channelStart - 1 + this.sacnUniverse.channelCount - 1;
//this.log.info('Updating slots buffer from ' + (startChannel-1) + ' - ' + endChannel);
//this.log.info('Color set to ' + r + '/' + g + '/' + b);
for (let idx = this.sacnUniverse.channelStart - 1; idx <= endChannel; idx++) {
this.sacnUniverse.sacnSlotsData[idx] = amount;
}
this.sacnUniverse.sacnClient.send(this.sacnUniverse.sacnPacket);
}
rgbToHsv(r, g, b) {
r /= 255, g /= 255, b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = max;
let s = max;
const v = max;
const d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0; // achromatic
}
else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
if (h !== undefined) {
h /= 6;
}
else {
h = 0;
}
}
return [h, s, v];
}
}
exports.DmxController = DmxController;
//# sourceMappingURL=dmx.js.map