const { HCColorLamp, StateUpdateManager } = require('homecontrol-control-base'); const { Control } = require('magic-home'); const mergeOptions = require('merge-options'); const types = Object.freeze({ type1: { rgb_min_0: true, ww_min_0: true, set_color_magic_bytes: [0xf0, 0x0f], wait_for_reply: true }, type2: { rgb_min_0: false, ww_min_0: false, set_color_magic_bytes: [0x00, 0x0f], wait_for_reply: false }, }); class HCMagicHome extends HCColorLamp { constructor(config) { super(config); let type = "type1"; if (this._configuration.type != undefined) { type = this._configuration.type; } if (!(type in types)) throw new Error(`Type "${type}" could not be found`); let characteristics = mergeOptions(types[type], this._configuration.characteristics); this._control = new Control(this._configuration.address, characteristics); this._sumanager = new StateUpdateManager(this._state); } // overwrite to make use of the SUManager get state() { return this._sumanager.state.clone(); } get effects() { return [ {name: "Seven Color Cross Fade SLOW", id: "slow-seven_color_cross_fade"}, {name: "Seven Color Strobe Flash SLOW", id: "slow-seven_color_strobe_flash"}, {name: "Strobe SLOW", id: "slow-white_strobe_flash"}, {name: "Seven Color Jump SLOW", id: "slow-seven_color_jumping"}, {name: "Seven Color Cross Fade MEDIUM", id: "medium-seven_color_cross_fade"}, {name: "Seven Color Strobe Flash MEDIUM", id: "medium-seven_color_strobe_flash"}, {name: "Strobe MEDIUM", id: "medium-white_strobe_flash"}, {name: "Seven Color Jump MEDIUM", id: "medium-seven_color_jumping"}, {name: "Seven Color Cross Fade FAST", id: "fast-seven_color_cross_fade"}, {name: "Seven Color Strobe Flash FAST", id: "fast-seven_color_strobe_flash"}, {name: "Strobe FAST", id: "fast-white_strobe_flash"}, {name: "Seven Color Jump FAST", id: "fast-seven_color_jumping"} ]; } turnOn() { let futureState = this.state; futureState.on = true; let suid = this._sumanager.registerUpdate(futureState); return new Promise((resolve, reject) => { this._control.turnOn((err) => { updateHelper(this, err, suid, resolve, reject); }); }); } turnOff() { let futureState = this.state; futureState.on = false; let suid = this._sumanager.registerUpdate(futureState); return new Promise((resolve, reject) => { this._control.turnOff((err) => { updateHelper(this, err, suid, resolve, reject); }); }); } togglePower() { if(this.state.on) { return this.turnOff(); } else { return this.turnOn(); } } setBrightness(brightness) { let futureState = this.state; futureState.brightness = brightness; let suid = this._sumanager.registerUpdate(futureState); let rgbColor = HSL_to_RGB(futureState.color); return new Promise((resolve, reject) => { this._control.setColorWithBrightness(rgbColor.red, rgbColor.green, rgbColor.blue, futureState.brightness, (err, success) => { updateHelper(this, err, suid, resolve, reject, success); }); }); } setColor(color) { let futureState = this.state; futureState.color = fillPartialHSL(color, futureState.color); let suid = this._sumanager.registerUpdate(futureState); let rgbColor = HSL_to_RGB(futureState.color); return new Promise((resolve, reject) => { this._control.setColorWithBrightness(rgbColor.red, rgbColor.green, rgbColor.blue, futureState.brightness, (err, success) => { updateHelper(this, err, suid, resolve, reject, success); }); }); } setEffect(id) { let matches = id.match(/([a-z]+)-(([a-zA-Z]|_)+)/); let speed = 60; let effect = "none"; if(matches != null) { effect = matches[2]; switch (matches[1]) { case 'slow': speed = 20; break; case 'fast': speed = 95; break; case 'medium': speed = 50; break; } } let futureState = this.state; if(effect == 'none') { futureState.effect = "none"; let suid = this._sumanager.registerUpdate(futureState); let rgbColor = HSL_to_RGB(futureState.color); return new Promise((resolve, reject) => { this._control.setColorWithBrightness(rgbColor.red, rgbColor.green, rgbColor.blue, futureState.brightness, (err, success) => { updateHelper(this, err, suid, resolve, reject, success); }); }); } else { futureState.effect = id; // id is "valid" / can be parsed let suid = this._sumanager.registerUpdate(futureState); return new Promise((resolve, reject) => { this._control.setPattern(effect, speed, (err, success) => { updateHelper(this, err, suid, resolve, reject, success); }); }); } } pullState() { return new Promise((resolve, reject) => { this._control.queryState((err, status) => { if(err) return reject(err); let currentState = this.state; let futureState = currentState.clone(); //console.log(status); futureState.on = status.on; futureState.brightness = extractBrightness(status.color); futureState.color = RGB_to_HSL(removeBrightness(status.color)); if (currentState.hash != futureState.hash) { // the state of the controller has changed externally this._sumanager.insertConfirmedState(futureState); this.emit("state change", this.state); } return resolve(); }); }); } } module.exports = HCMagicHome; function updateHelper(self, err, suid, resolve, reject, resolveVal) { if (err) { self._sumanager.rejectUpdate(suid); return reject(err); } self._sumanager.confirmUpdate(suid); if (self._sumanager.highestConfirmedId == suid) { self.emit("state change", self.state); } return resolve(resolveVal); } function HSL_to_RGB(hsl) { var h = hsl.hue / 360; var s = hsl.sat / 100; var l = hsl.l / 100; var r, g, b; if (s == 0) { r = g = b = l; // achromatic } else { function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return { red: Math.round(r * 255), green: Math.round(g * 255), blue: Math.round(b * 255) }; } function RGB_to_HSL(rgb) { var r = rgb.red / 255; var g = rgb.green / 255; var b = rgb.blue / 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); if (max == min || max - min < 0.01) { h = s = 0; // achromatic } else { var d = max - min; s = d / (2 - max - min); 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; } h /= 6; } var l = 1 - 0.5 * s; return { hue: Math.round(h * 360), sat: Math.round(s * 100), l: Math.round(l * 100) }; } function extractBrightness(rgb) { var max = Math.max(rgb.red, rgb.green, rgb.blue); return (max / 255) * 100; } function removeBrightness(rgb) { var max = Math.max(rgb.red, rgb.green, rgb.blue); var scale = (max > 0) ? 255 / max : 0; return { red: rgb.red * scale, green: rgb.green * scale, blue: rgb.blue * scale, }; } function fillPartialHSL(newcolor, oldcolor) { if(newcolor.sat != undefined && newcolor.l == undefined) { newcolor.l = (newcolor.sat > 50) ? 50 : 100 - newcolor.sat; } //console.log(newcolor, oldcolor); var color = {}; color["hue"] = (newcolor.hue != null) ? newcolor.hue : oldcolor.hue; color["sat"] = (newcolor.sat != null) ? newcolor.sat : oldcolor.sat; color["l"] = (newcolor.l != null) ? newcolor.l : oldcolor.l; return color; }