diff --git a/HCMagicHome.js b/HCMagicHome.js index 3f1059f..d0e7372 100644 --- a/HCMagicHome.js +++ b/HCMagicHome.js @@ -1,5 +1,6 @@ const { - HCColorLamp + HCColorLamp, + StateUpdateManager } = require('homecontrol-control-base'); const { Control } = require('magic-home'); @@ -9,34 +10,60 @@ class HCMagicHome extends HCColorLamp { super(config); this._control = new Control(this._configuration.address, this._configuration.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) => { - if (err) return reject(err); - - this._state.on = true; - this.emit("state change", this.state); - return resolve(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) => { - if (err) return reject(err); - - this._state.on = false; - this.emit("state change", this.state); - return resolve(err); + updateHelper(this, err, suid, resolve, reject); }); }); } toggle() { - if(this._state.on) { + if(this.state.on) { return this.turnOff(); } else { return this.turnOn(); @@ -44,16 +71,212 @@ class HCMagicHome extends HCColorLamp { } changeBrightness(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); + }); + }); } - changeColor(hue, sat, light) { + changeColor(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); + } + + resolve(); + }); + }); } } -module.exports = HCMagicHome; \ No newline at end of file +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; +} diff --git a/package-lock.json b/package-lock.json index 90d0e3a..603dbd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,10 +4,35 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "homecontrol-control-base": { + "version": "git+https://git.literalchaos.de/jan/homecontrol-control-base.git#5d58715922bdc7abf909637f8f959e4a95c3c53b", + "requires": { + "merge-options": "1.0.1", + "node-object-hash": "1.4.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, "magic-home": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/magic-home/-/magic-home-1.4.0.tgz", "integrity": "sha512-hSq3C/9rbEH3RB2t2rG/LsSb2vpalzIGh/mLKJyjaki3Wbvt9ss+2OUJ1JsAOAjjgkvxDEfbQ044yFsHZUS75A==" + }, + "merge-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", + "integrity": "sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==", + "requires": { + "is-plain-obj": "1.1.0" + } + }, + "node-object-hash": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/node-object-hash/-/node-object-hash-1.4.1.tgz", + "integrity": "sha512-JQVqSM5/mOaUoUhCYR0t1vgm8RFo7qpJtPvnoFCLeqQh1xrfmr3BCD3nGBnACzpIEF7F7EVgqGD3O4lao/BY/A==" } } } diff --git a/package.json b/package.json index 6c6cd1d..70530a4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "Jan Scheiper", "license": "UNLICENSED", "dependencies": { + "homecontrol-control-base": "git+https://git.literalchaos.de/jan/homecontrol-control-base.git", "magic-home": "^1.4.0" } }