334 lines
9.8 KiB
JavaScript
334 lines
9.8 KiB
JavaScript
const {
|
|
HCColorLamp,
|
|
StateUpdateManager,
|
|
utils,
|
|
} = require('homecontrol-control-base');
|
|
|
|
const { ControlAddressable, AddressableColorStopMode } = require('magic-home');
|
|
|
|
const RBM_EFFECTS = new Array(100).fill(null).map((_,i) => {
|
|
return { name: `Random Effect ${i}`, id: `rbm-${i}` };
|
|
});
|
|
|
|
class HCMagicHomeAddressable extends HCColorLamp {
|
|
constructor(config, registry) {
|
|
super(config);
|
|
|
|
if (!("address" in this._configuration)) {
|
|
throw new Error(`Required configuration field "address" is missing"`);
|
|
}
|
|
if (!("strip_length" in this._configuration)) {
|
|
throw new Error(`Required configuration field "strip_length" is missing"`);
|
|
}
|
|
|
|
const opts = {
|
|
log_all_received: (this._configuration.logall !== undefined) ? this._configuration.logall : false,
|
|
connect_timeout: (this._configuration.connect_timeout !== undefined) ? this._configuration.connect_timeout : 10000,
|
|
command_timeout: (this._configuration.command_timeout !== undefined) ? this._configuration.command_timeout : 5000,
|
|
};
|
|
|
|
this._control = new ControlAddressable(this._configuration.address, opts);
|
|
|
|
this._sumanager = new StateUpdateManager(this._state);
|
|
this._registry = registry;
|
|
|
|
if ("registry_key" in this._configuration) {
|
|
this._registry[this._configuration.registry_key] = this;
|
|
}
|
|
|
|
this._subsections = [];
|
|
|
|
this._initialUpdate = true;
|
|
}
|
|
|
|
async deinit() {
|
|
if ("registry_key" in this._configuration) {
|
|
delete this._registry[this._configuration.registry_key];
|
|
}
|
|
}
|
|
|
|
static get version() {
|
|
return require("./package.json").version;
|
|
}
|
|
|
|
// overwrite to make use of the SUManager
|
|
get state() {
|
|
return this._sumanager.state.clone();
|
|
}
|
|
|
|
get effects() {
|
|
return [
|
|
{ name: "Subsection Color Mode", id: "subsections" },
|
|
...RBM_EFFECTS,
|
|
];
|
|
}
|
|
|
|
_createSubsectionMode() {
|
|
const currentState = this.state;
|
|
const mode = new AddressableColorStopMode(this._configuration.strip_length);
|
|
|
|
let { red: bgRed, green: bgGreen, blue: bgBlue } = utils.HSL_to_RGB(currentState.color);
|
|
|
|
bgRed = Math.round(bgRed * currentState.brightness / 100);
|
|
bgGreen = Math.round(bgGreen * currentState.brightness / 100);
|
|
bgBlue = Math.round(bgBlue * currentState.brightness / 100);
|
|
|
|
mode.addColorStop(0, bgRed, bgGreen, bgBlue);
|
|
|
|
for (let i = 0; i < this._subsections.length; i++) {
|
|
const ss = this._subsections[i];
|
|
|
|
if (ss.start >= this._configuration.strip_length) {
|
|
break;
|
|
}
|
|
|
|
mode.addColorStop(ss.start, ss.color.red, ss.color.green, ss.color.blue);
|
|
|
|
if (i < this._subsections.length - 1) {
|
|
if (this._subsections[i+1].start > ss.end && ss.end < this._configuration.strip_length) {
|
|
mode.addColorStop(ss.end, bgRed, bgGreen, bgBlue);
|
|
}
|
|
} else {
|
|
if (ss.end < this._configuration.strip_length) {
|
|
mode.addColorStop(ss.end, bgRed, bgGreen, bgBlue);
|
|
}
|
|
}
|
|
}
|
|
|
|
return mode;
|
|
}
|
|
|
|
turnOn() {
|
|
let futureState = this.state;
|
|
futureState.on = true;
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
return this._control.setPower(true).then(success => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return success;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
turnOff() {
|
|
let futureState = this.state;
|
|
futureState.on = false;
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
return this._control.setPower(false).then(success => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return success;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
// color = {red, green, blue}
|
|
setSubsectionColor(start, end, ssid, color) {
|
|
this._subsections = this._subsections.filter(ss => ss.id != ssid);
|
|
|
|
this._subsections.push({ start, end, id: ssid, color });
|
|
|
|
this._subsections.sort((a,b) => a.start - b.start);
|
|
|
|
return this.setEffect("subsections");
|
|
}
|
|
|
|
removeSubsection(ssid) {
|
|
this._subsections = this._subsections.filter(ss => ss.id != ssid);
|
|
|
|
if (this._subsections.length > 0) {
|
|
return this.setEffect("subsections");
|
|
} else {
|
|
return this.setEffect("none");
|
|
}
|
|
}
|
|
|
|
isSubsectionActive(ssid) {
|
|
return this.state.on && this.state.effect === "subsections" && this._subsections.find(ss => ss.id === ssid) !== null;
|
|
}
|
|
|
|
setBrightness(brightness) {
|
|
let futureState = this.state;
|
|
futureState.brightness = brightness;
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
let promise;
|
|
|
|
if (!["none", "subsections"].includes(futureState.effect)) {
|
|
promise = this.setEffect(futureState.effect);
|
|
} else {
|
|
promise = this.setColor(futureState.color);
|
|
}
|
|
|
|
return promise.then(success => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return success;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
setColor(color) {
|
|
const futureState = this.state;
|
|
futureState.color = utils.fillPartialHSL(color, futureState.color);
|
|
futureState.on = true; // setting color turns on the controller
|
|
|
|
let { red, green, blue } = utils.HSL_to_RGB(futureState.color);
|
|
|
|
red = Math.round(red * futureState.brightness / 100);
|
|
green = Math.round(green * futureState.brightness / 100);
|
|
blue = Math.round(blue * futureState.brightness / 100);
|
|
|
|
let promise;
|
|
|
|
if (futureState.effect !== "subsections") {
|
|
futureState.effect = "none";
|
|
const suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
promise = this._control.setFixedMode({
|
|
effect: 1,
|
|
foreground: { red, green, blue }
|
|
}).then(success => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return success;
|
|
});
|
|
} else {
|
|
const suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
const mode = this._createSubsectionMode();
|
|
|
|
return this._control.setMultiColorMode(mode).then(success => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return success;
|
|
});
|
|
}
|
|
|
|
return promise.catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
setEffect(id) {
|
|
let futureState = this.state;
|
|
futureState.effect = id;
|
|
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
let promise = this.turnOn();
|
|
|
|
if (futureState.effect == "none" || futureState.effect == "subsections") {
|
|
promise = this.setColor(futureState.color).then(success => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return success;
|
|
})
|
|
} else {
|
|
const rbmMatch = id.match(/rbm\-(\d+)/);
|
|
|
|
if (rbmMatch === null) {
|
|
return Promise.reject();
|
|
}
|
|
|
|
const rbmMode = Number(rbmMatch[1]);
|
|
|
|
promise = this._control.setRbmMode(rbmMode, futureState.brightness, 100).then(success => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return success;
|
|
});
|
|
}
|
|
|
|
return promise.catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
pullState() {
|
|
return this._control.queryState().then(status => {
|
|
let currentState = this.state;
|
|
let futureState = currentState.clone();
|
|
|
|
// console.log(status);
|
|
|
|
futureState.on = status.on;
|
|
|
|
// let effect = (status.mode != 'color' && status.mode != 'warm_white' && status.mode != 'special') ? status.mode : 'none';
|
|
// if(effect != 'none') {
|
|
// let speed = (status.speed > 80) ? 'fast' : (status.speed > 30) ? 'medium' : 'slow';
|
|
// futureState.effect = speed + '-' + effect;
|
|
// } else {
|
|
// futureState.effect = 'none';
|
|
// }
|
|
|
|
if (status.mode == "fixed" || this._initialUpdate) { // only update color when no effects are playing
|
|
futureState.brightness = extractBrightness(status.color);
|
|
futureState.color = utils.RGB_to_HSL(removeBrightness(status.color));
|
|
}
|
|
|
|
if (status.mode === "rbm") {
|
|
futureState.effect = "rbm-" + status.effect;
|
|
}
|
|
|
|
// console.log(futureState);
|
|
|
|
if (currentState.hash != futureState.hash) {
|
|
// the state of the controller has changed externally
|
|
this._sumanager.insertConfirmedState(futureState);
|
|
this.emit("state change", this.state);
|
|
}
|
|
|
|
this._initialUpdate = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = HCMagicHomeAddressable;
|
|
|
|
function extractBrightness(rgb) {
|
|
let max = Math.max(rgb.red, rgb.green, rgb.blue);
|
|
return (max / 255) * 100;
|
|
}
|
|
|
|
function removeBrightness(rgb) {
|
|
let max = Math.max(rgb.red, rgb.green, rgb.blue);
|
|
let scale = (max > 0) ? 255 / max : 0;
|
|
|
|
return {
|
|
red: rgb.red * scale,
|
|
green: rgb.green * scale,
|
|
blue: rgb.blue * scale,
|
|
};
|
|
} |