@elshaer/homebridge-hdl-buspro-enhanced
Version:
Linking the HDL bus into the Homebridge widget
259 lines • 9.11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RelayRGB = void 0;
const RelayLightbulb_1 = require("./RelayLightbulb");
class RelayRGB {
constructor(platform, accessory, name, controller, device, redChannel, greenChannel, blueChannel) {
this.platform = platform;
this.accessory = accessory;
this.name = name;
this.controller = controller;
this.device = device;
this.redChannel = redChannel;
this.greenChannel = greenChannel;
this.blueChannel = blueChannel;
this.RGBState = {
On: false,
Hue: 0,
Saturation: 100,
Brightness: 100,
};
this.lastColorState = {
Hue: 0,
Saturation: 100,
Brightness: 100,
};
this.lastLevels = { red: 0, green: 0, blue: 0 };
this.ignoreExternalUpdates = false;
this.updateTimeout = null;
this.isRestoringState = false;
const Service = this.platform.Service;
const Characteristic = this.platform.Characteristic;
this.service = this.accessory.getService(Service.Lightbulb) ||
this.accessory.addService(Service.Lightbulb);
this.service.setCharacteristic(Characteristic.Name, name);
this.service.getCharacteristic(Characteristic.On)
.onGet(this.getOn.bind(this))
.onSet(this.setOn.bind(this));
this.service.getCharacteristic(Characteristic.Hue)
.onGet(this.getHue.bind(this))
.onSet(this.setHue.bind(this));
this.service.getCharacteristic(Characteristic.Saturation)
.onGet(this.getSaturation.bind(this))
.onSet(this.setSaturation.bind(this));
this.service.getCharacteristic(Characteristic.Brightness)
.onGet(this.getBrightness.bind(this))
.onSet(this.setBrightness.bind(this));
this.listener = new RelayLightbulb_1.RelayListener(this.device, this.controller);
this.listenForExternalUpdates();
}
async getOn() {
return this.RGBState.On;
}
async setOn(value) {
const wasOn = this.RGBState.On;
this.RGBState.On = value;
if (this.RGBState.On && !wasOn) {
this.isRestoringState = true;
this.RGBState.Hue = this.lastColorState.Hue;
this.RGBState.Saturation = this.lastColorState.Saturation;
this.RGBState.Brightness = this.lastColorState.Brightness;
// Immediate UI update
this.service.updateCharacteristic(this.platform.Characteristic.Hue, this.RGBState.Hue);
this.service.updateCharacteristic(this.platform.Characteristic.Saturation, this.RGBState.Saturation);
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.RGBState.Brightness);
}
else if (!this.RGBState.On && wasOn) {
this.lastColorState = {
Hue: this.RGBState.Hue,
Saturation: this.RGBState.Saturation,
Brightness: this.RGBState.Brightness,
};
}
this.service.updateCharacteristic(this.platform.Characteristic.On, this.RGBState.On);
this.debouncedUpdate();
}
async getHue() {
return this.RGBState.Hue;
}
async setHue(value) {
this.RGBState.Hue = value;
this.updateColorState();
this.debouncedUpdate();
}
async getSaturation() {
return this.RGBState.Saturation;
}
async setSaturation(value) {
this.RGBState.Saturation = value;
this.updateColorState();
this.debouncedUpdate();
}
async getBrightness() {
return this.RGBState.Brightness;
}
async setBrightness(value) {
this.RGBState.Brightness = value;
this.updateColorState();
this.debouncedUpdate();
}
updateColorState() {
this.lastColorState = { ...this.RGBState };
}
debouncedUpdate() {
if (this.updateTimeout) {
clearTimeout(this.updateTimeout);
}
this.updateTimeout = setTimeout(() => this.updateRGBLights(), 50);
}
updateRGBLights() {
if (!this.RGBState.On) {
this.sendAtomicCommand([
{ channel: this.redChannel, level: 0 },
{ channel: this.greenChannel, level: 0 },
{ channel: this.blueChannel, level: 0 },
]);
return;
}
const { r, g, b } = this.hsvToRgb(this.RGBState.Hue, this.RGBState.Saturation, this.RGBState.Brightness);
this.sendAtomicCommand([
{ channel: this.redChannel, level: r },
{ channel: this.greenChannel, level: g },
{ channel: this.blueChannel, level: b },
]);
this.isRestoringState = false;
}
sendAtomicCommand(commands) {
this.ignoreExternalUpdates = true;
commands.forEach(cmd => {
if (cmd.channel === undefined) {
return;
}
this.controller.send({
target: this.device,
command: 0x0031,
data: { channel: cmd.channel, level: cmd.level },
});
});
setTimeout(() => {
this.ignoreExternalUpdates = false;
}, 300); // Increased timeout for hardware response
}
hsvToRgb(h, s, v) {
h = Math.max(0, Math.min(360, h));
s = Math.max(0, Math.min(100, s)) / 100;
v = Math.max(0, Math.min(100, v)) / 100;
if (s < 0.05) {
const whiteLevel = Math.round(v * 100);
return { r: whiteLevel, g: whiteLevel, b: whiteLevel };
}
const i = Math.floor(h / 60);
const f = (h / 60) - i;
const p = v * (1 - s);
const q = v * (1 - s * f);
const t = v * (1 - s * (1 - f));
let r, g, b;
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;
default:
r = 0;
g = 0;
b = 0;
}
return {
r: Math.round(r * 100),
g: Math.round(g * 100),
b: Math.round(b * 100),
};
}
listenForExternalUpdates() {
const createHandler = (color) => (level) => {
this.lastLevels[color] = level;
this.handleExternalUpdate();
};
this.listener.getChannelEventEmitter(this.redChannel).on('update', createHandler('red'));
this.listener.getChannelEventEmitter(this.greenChannel).on('update', createHandler('green'));
this.listener.getChannelEventEmitter(this.blueChannel).on('update', createHandler('blue'));
}
handleExternalUpdate() {
if (this.ignoreExternalUpdates || this.isRestoringState) {
return;
}
const { h, s, v } = this.rgbToHsv(this.lastLevels.red, this.lastLevels.green, this.lastLevels.blue);
this.RGBState.On = v > 0;
this.RGBState.Brightness = v;
if (this.RGBState.On) {
this.RGBState.Hue = h;
this.RGBState.Saturation = s;
this.lastColorState = { Hue: h, Saturation: s, Brightness: v };
}
this.service.updateCharacteristic(this.platform.Characteristic.On, this.RGBState.On);
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, v);
if (this.RGBState.On) {
this.service.updateCharacteristic(this.platform.Characteristic.Hue, h);
this.service.updateCharacteristic(this.platform.Characteristic.Saturation, s);
}
}
rgbToHsv(r, g, b) {
r /= 100;
g /= 100;
b /= 100;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
let h = 0;
if (delta !== 0) {
if (max === r) {
h = ((g - b) / delta) % 6;
}
else if (max === g) {
h = (b - r) / delta + 2;
}
else {
h = (r - g) / delta + 4;
}
h = Math.round(h * 60);
if (h < 0) {
h += 360;
}
}
const s = max === 0 ? 0 : (delta / max) * 100;
const v = max * 100;
return {
h: Math.round(h),
s: Math.round(s),
v: Math.round(v),
};
}
}
exports.RelayRGB = RelayRGB;
//# sourceMappingURL=RelayRGB.js.map