UNPKG

node-tradfri-client

Version:

Library to talk to IKEA Trådfri Gateways without external binaries

275 lines (274 loc) 9.56 kB
"use strict"; // tslint:disable:variable-name Object.defineProperty(exports, "__esModule", { value: true }); exports.conversions = exports.deserializers = exports.serializers = void 0; const math_1 = require("alcalzone-shared/math"); const strings_1 = require("alcalzone-shared/strings"); const predefined_colors_1 = require("./predefined-colors"); // ========================== // WHITE SPECTRUM conversions // interpolate from [0..100%] to [250..454] const colorTemperature_out = (value) => { const [min, max] = predefined_colors_1.colorTemperatureRange; // extrapolate 0-100% to [min..max] value = (0, math_1.clamp)(value, 0, 100); return (0, math_1.roundTo)(min + (value / 100) * (max - min), 0); }; // interpolate from [250..454] to [0..100%] const colorTemperature_in = (value) => { const [min, max] = predefined_colors_1.colorTemperatureRange; // interpolate "color percentage" from the colorTemperature range of a lightbulb value = (value - min) / (max - min); value = (0, math_1.clamp)(value, 0, 1); return (0, math_1.roundTo)(value * 100, 1); }; // // ========================== // // RGB conversions // // Tradfri lights seem to be Philips Hue Gamut B, see // // https://developers.meethue.com/documentation/core-concepts#color_gets_more_complicated // // Conversion based on // // https://github.com/Q42/Q42.HueApi/blob/master/src/Q42.HueApi.ColorConverters/OriginalWithModel/HueColorConverter.cs // function rgbToCIExyY(r: number, g: number, b: number) { // // transform [0..255] => [0..1] // [r, g, b] = [r, g, b].map(c => c / 255); // // gamma correction // [r, g, b] = [r, g, b].map(c => (c > 0.04045) ? ((c + 0.055) / 1.055) ** 2.4 : c / 12.92); // // transform using custom RGB->XYZ matrix. See comment in Q42 // const X = r * 0.664511 + g * 0.154324 + b * 0.162028; // const Y = r * 0.283881 + g * 0.668433 + b * 0.047685; // const Z = r * 0.000088 + g * 0.072310 + b * 0.986039; // let [x, y] = [0, 0]; // if (X + Y + Z > 0) { // // calculate CIE xy // x = X / (X + Y + Z); // y = Y / (X + Y + Z); // } // ({x, y} = mapToGamut(x, y, DEFAULT_GAMUT)); // return {x, y, Y}; // } // function rgbFromCIExyY(x: number, y: number, Y: number = 1) { // // Map the given point to lie inside the bulb gamut // ({x, y} = mapToGamut(x, y, DEFAULT_GAMUT)); // // calculate X/Y/Z // const z = 1 - x - y; // const X = (Y / y) * x; // const Z = (Y / y) * z; // // convert to RGB // let r = X * 1.656492 - Y * 0.354851 - Z * 0.255038; // let g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; // let b = X * 0.051713 - Y * 0.121364 + Z * 1.011530; // // downscale so the maximum component is not > 1 // const maxRGB = Math.max(r, g, b); // if (maxRGB > 1) { // [r, g, b] = [r, g, b].map(c => c / maxRGB); // } // // reverse gamma correction // [r, g, b] = [r, g, b].map(c => c <= 0.0031308 ? 12.92 * c : (1.0 + 0.055) * c ** (1.0 / 2.4) - 0.055); // // transform back to [0..255] // [r, g, b] = [r, g, b].map(c => Math.round(clamp(c, 0, 1) * 255)); // return {r, g, b}; // } // /** // * Describes the RGB triangle supported by a lightbulb // * in x-y coordinates // */ // interface Gamut { // red: Point; // green: Point; // blue: Point; // } // const DEFAULT_GAMUT: Gamut = { // red: {x: 0.700607, y: 0.299301}, // green: {x: 0.172416, y: 0.746797}, // blue: {x: 0.135503, y: 0.039879}, // }; // function mapToGamut(x: number, y: number, gamut: Gamut): Point { // const point = {x, y}; // const gamutAsTriangle: Triangle = [gamut.red, gamut.green, gamut.blue]; // if (!pointInTriangle(gamutAsTriangle, point)) { // const closestEdge = findClosestTriangleEdge(point, gamutAsTriangle); // const projected = projectPointOnEdge(point, closestEdge); // return projected; // } else { // return {x, y}; // } // } function rgbToHSV(r, g, b) { // transform [0..255] => [0..1] [r, g, b] = [r, g, b].map((c) => c / 255); const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h; let s; const v = (0, math_1.roundTo)(max, 2); if (r === g && g === b) { h = 0; } else if (max === r) { h = 60 * (0 + (g - b) / (max - min)); } else if (max === g) { h = 60 * (2 + (b - r) / (max - min)); } else if (max === b) { h = 60 * (4 + (r - g) / (max - min)); } h = Math.round(h); // h is defined, we checked all possibilities if (h < 0) h += 360; if (max === 0) { s = 0; } else { s = (0, math_1.roundTo)((max - min) / max, 3); } return { h, s, v }; } function rgbFromHSV(h, s, v) { let r; let g; let b; if (s === 0) { r = g = b = v; } else { const hi = Math.floor(h / 60); const f = h / 60 - hi; const p = v * (1 - s); const q = v * (1 - s * f); const t = v * (1 - s * (1 - f)); switch (hi) { case 0: case 6: [r, g, b] = [v, t, p]; break; case 1: [r, g, b] = [q, v, p]; break; case 2: [r, g, b] = [p, v, t]; break; case 3: [r, g, b] = [p, q, v]; break; case 4: [r, g, b] = [t, p, v]; break; case 5: [r, g, b] = [v, p, q]; break; } } // transform back to [0..255] - r, g and b are defined always [r, g, b] = [r, g, b].map((c) => Math.round((0, math_1.clamp)(c, 0, 1) * 255)); return { r, g, b }; } function rgbToString(r, g, b) { return [r, g, b].map((c) => (0, strings_1.padStart)(c.toString(16), 2, "0")).join(""); } function rgbFromString(rgb) { const r = parseInt(rgb.substr(0, 2), 16); const g = parseInt(rgb.substr(2, 2), 16); const b = parseInt(rgb.substr(4, 2), 16); return { r, g, b }; } // =========================== // RGB serializers // interpolate hue from [0..360] to [0..COLOR_MAX] const hue_out = (value, light) => { if (light != null && light.spectrum !== "rgb") return null; // hue is not supported value = (0, math_1.clamp)(value, 0, 360); return (0, math_1.roundTo)((value / 360) * predefined_colors_1.MAX_COLOR, 0); }; // interpolate hue from [0..COLOR_MAX] to [0..360] const hue_in = (value /*, light: Light*/) => { value = (0, math_1.clamp)(value / predefined_colors_1.MAX_COLOR, 0, 1); return (0, math_1.roundTo)(value * 360, 1); }; // interpolate saturation from [0..100%] to [0..COLOR_MAX] const saturation_out = (value, light) => { if (light != null && light.spectrum !== "rgb") return null; // hue is not supported value = (0, math_1.clamp)(value, 0, 100); return (0, math_1.roundTo)((value / 100) * predefined_colors_1.MAX_COLOR, 0); }; // interpolate saturation from [0..COLOR_MAX] to [0..100%] const saturation_in = (value /*, light: Light*/) => { value = (0, math_1.clamp)(value / predefined_colors_1.MAX_COLOR, 0, 1); return (0, math_1.roundTo)(value * 100, 1); }; // =========================== // TRANSITION TIME conversions // the sent value is in 10ths of seconds, we're working with seconds const transitionTime_out = (val) => val && val * 10; // "val && " avoids sending `null` if val is null for some reason // the sent value is in 10ths of seconds, we're working with seconds const transitionTime_in = (val) => val / 10; // =========================== // BRIGHTNESS conversions // interpolate from [0..100%] to [0..254] const brightness_out = (value) => { value = (0, math_1.clamp)(value, 0, 100); return (0, math_1.roundTo)((value / 100) * 254, 0); }; // interpolate from [0..254] to [0..100%] const brightness_in = (value) => { value = (0, math_1.clamp)(value, 0, 254); if (value === 0) return 0; value = (value / 254) * 100; return (0, math_1.roundTo)(value, 1); }; // =========================== // BLIND POSITION conversions // The gateway expects 0 to be open and 100 to be closed // we do the opposite const position_out = (val) => 100 - val; // the sent value is in 10ths of seconds, we're working with seconds const position_in = position_out; // =========================== // AIR PURIFIER conversions // The fan speed only accepts certain values const fan_speed_out = (val) => { if (val === 0) return 0; if (val < 10) return 10; if (val > 50) return 50; // Round to the nearest multiple of 5 return Math.round(val / 5) * 5; }; // =========================== // PRIMITIVE conversions // Some values are boolean but have an inverted meaning const boolean_inverted_out = (bool) => bool ? 0 : 1; // the sent value is in 10ths of seconds, we're working with seconds const boolean_inverted_in = (raw) => !(raw === 1 || raw === "true" || raw === "on" || raw === true); exports.serializers = { transitionTime: transitionTime_out, hue: hue_out, saturation: saturation_out, brightness: brightness_out, colorTemperature: colorTemperature_out, position: position_out, booleanInverted: boolean_inverted_out, fanSpeed: fan_speed_out, }; exports.deserializers = { transitionTime: transitionTime_in, hue: hue_in, saturation: saturation_in, brightness: brightness_in, colorTemperature: colorTemperature_in, position: position_in, booleanInverted: boolean_inverted_in, }; exports.conversions = { // rgbFromCIExyY, // rgbToCIExyY, rgbFromHSV, rgbToHSV, rgbToString, rgbFromString, };