const { HCColorLamp, StateUpdateManager } = require('homecontrol-control-base'); const { Control } = require('magic-home'); class HCMagicHome extends HCColorLamp { constructor(config) { super(config); if (!("address" in this._configuration)) { throw new Error(`Required configuration field "address" is missing"`); } let opts = { ack: (this._configuration.ack !== undefined) ? Control.ackMask(this._configuration.ack) : true, 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 Control(this._configuration.address, opts); 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 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; }); } togglePower() { if(this.state.on) { return this.turnOff(); } else { return this.turnOn(); } } setBrightness(brightness) { let futureState = this.state; futureState.brightness = brightness; futureState.effect = "none"; let suid = this._sumanager.registerUpdate(futureState); let { red, green, blue } = HSL_to_RGB(futureState.color); return this._control.setColorWithBrightness(red, green, blue, futureState.brightness).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) { let futureState = this.state; futureState.color = fillPartialHSL(color, futureState.color); futureState.effect = "none"; let suid = this._sumanager.registerUpdate(futureState); let { red, green, blue } = HSL_to_RGB(futureState.color); return this._control.setColorWithBrightness(red, green, blue, futureState.brightness).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; }); } 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; let promise; if(effect == 'none') { futureState.effect = "none"; let { red, green, blue } = HSL_to_RGB(futureState.color); promise = this._control.setColorWithBrightness(red, green, blue, futureState.brightness); } else { futureState.effect = id; // id is "valid" / can be parsed promise = this._control.setPattern(effect, speed); } let suid = this._sumanager.registerUpdate(futureState); 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; }); } 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 (futureState.effect == 'none') { // only update color when no effects are playing 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); } }); } } module.exports = HCMagicHome; function HSL_to_RGB(hsl) { let h = hsl.hue / 360; let s = hsl.sat / 100; let l = hsl.l / 100; let r, g, b; if (s == 0) { r = g = b = l; // achromatic } else { 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 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; } function RGB_to_HSL(rgb) { let r = rgb.red / 255; let g = rgb.green / 255; let b = rgb.blue / 255; let 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; } let l = 1 - 0.5 * s; return { hue: Math.round(h * 360), sat: Math.round(s * 100), l: Math.round(l * 100) }; } 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, }; } function fillPartialHSL(newcolor, oldcolor) { if(newcolor.sat != undefined && newcolor.l == undefined) { newcolor.l = (newcolor.sat > 50) ? 50 : 100 - newcolor.sat; } //console.log(newcolor, oldcolor); let 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; }