hc-magichome/HCMagicHome.js
2019-01-29 22:10:49 +01:00

312 lines
7.6 KiB
JavaScript

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);
if (!("address" in this._configuration)) {
throw new Error(`Required configuration field "address" is missing"`);
}
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;
}