UNPKG

@pajn/node-tradfri-client

Version:

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

486 lines (485 loc) 20.1 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Light = exports.PowerRestoredAction = void 0; const math_1 = require("alcalzone-shared/math"); const accessory_1 = require("./accessory"); const conversions_1 = require("./conversions"); const ipsoDevice_1 = require("./ipsoDevice"); const ipsoObject_1 = require("./ipsoObject"); const predefined_colors_1 = require("./predefined-colors"); var PowerRestoredAction; (function (PowerRestoredAction) { PowerRestoredAction[PowerRestoredAction["TurnOn"] = 2] = "TurnOn"; PowerRestoredAction[PowerRestoredAction["RememberStatus"] = 4] = "RememberStatus"; })(PowerRestoredAction = exports.PowerRestoredAction || (exports.PowerRestoredAction = {})); class Light extends ipsoDevice_1.IPSODevice { constructor(options, accessory) { super(options); // All properties only exist after the light has been received from the gateway // so they are definitely assigned! this.color = "f1e0b5"; // hex string this.transitionTime = 0.5; // <float> // In order for the simplified API to work, the // accessory reference must be a proxy if (accessory != null && !accessory.isProxy) { accessory = accessory.createProxy(); } this._accessory = accessory; // get the model number to detect features if (accessory != null && accessory.deviceInfo != null && accessory.deviceInfo.modelNumber != null && accessory.deviceInfo.modelNumber.length > 0) { this._modelName = accessory.deviceInfo.modelNumber; } } /** * Returns true if the current lightbulb is dimmable */ get isDimmable() { return true; // we know no lightbulbs that aren't dimmable } /** * Returns true if the current lightbulb is switchable */ get isSwitchable() { return true; // we know no lightbulbs that aren't switchable } clone() { const ret = super.clone(this._accessory); ret._modelName = this._modelName; return ret; } get spectrum() { if (this._spectrum == null) { // determine the spectrum if (this.hue != null && this.saturation != null) { this._spectrum = "rgb"; } else if (this.colorTemperature != null) { this._spectrum = "white"; } else { this._spectrum = "none"; } } return this._spectrum; } /** * Creates a proxy which redirects the properties to the correct internal one */ createProxy() { const raw = this._accessory instanceof accessory_1.Accessory ? this._accessory.options.skipValueSerializers : false; switch (this.spectrum) { case "rgb": { const proxy = createRGBProxy(raw); return super.createProxy(proxy.get, proxy.set); } case "none": { // Some lights might only report color in CIE xy if (this.colorX !== undefined && this.colorY !== undefined && this.spectrum === 'none') { const proxy = createRGBCIExyProxy(); return super.createProxy(proxy.get, proxy.set); } } default: return this; } } // ================================= // Simplified API access /** * Ensures this instance is linked to a tradfri client and an accessory * @throws Throws an error if it isn't */ ensureLink() { if (this.client == null) { throw new Error("Cannot use the simplified API on devices which aren't linked to a client instance."); } if (!(this._accessory instanceof accessory_1.Accessory)) { throw new Error("Cannot use the simplified API on lightbulbs which aren't linked to an Accessory instance."); } } /** Turn this lightbulb on */ turnOn() { this.ensureLink(); return this.client.operateLight(this._accessory, { onOff: true, }); } /** Turn this lightbulb off */ turnOff() { this.ensureLink(); return this.client.operateLight(this._accessory, { onOff: false, }); } /** Toggles this lightbulb on or off */ toggle(value = !this.onOff) { this.ensureLink(); return this.client.operateLight(this._accessory, { onOff: value, }); } operateLight(operation, transitionTime) { this.ensureLink(); if (transitionTime != null) { transitionTime = Math.max(0, transitionTime); operation.transitionTime = transitionTime; } return this.client.operateLight(this._accessory, operation); } /** * Changes this lightbulb's brightness * @returns true if a request was sent, false otherwise */ setBrightness(value, transitionTime) { this.ensureLink(); value = math_1.clamp(value, 0, 100); return this.operateLight({ dimmer: value, }, transitionTime); } /** * Changes this lightbulb's color * @param value The target color as a 6-digit hex string * @returns true if a request was sent, false otherwise */ setColor(value, transitionTime) { this.ensureLink(); switch (this.spectrum) { case "rgb": { return this.operateLight({ color: value, }, transitionTime); } case "white": { // We make an exception for the predefined white spectrum colors if (!(value in predefined_colors_1.whiteSpectrumHex)) { throw new Error("White spectrum bulbs only support the following colors: " + Object.keys(predefined_colors_1.whiteSpectrumHex).join(", ")); } return this.operateLight({ colorTemperature: predefined_colors_1.whiteSpectrumHex[value], }, transitionTime); } default: { throw new Error("setColor is only available for RGB lightbulbs"); } } } /** * Changes this lightbulb's color temperature * @param value The target color temperature in the range 0% (cold) to 100% (warm) * @returns true if a request was sent, false otherwise */ setColorTemperature(value, transitionTime) { this.ensureLink(); if (this.spectrum !== "white") throw new Error("setColorTemperature is only available for white spectrum lightbulbs"); value = math_1.clamp(value, 0, 100); return this.operateLight({ colorTemperature: value, }, transitionTime); } /** * Changes this lightbulb's color hue * @returns true if a request was sent, false otherwise */ setHue(value, transitionTime) { this.ensureLink(); if (this.spectrum !== "rgb") throw new Error("setHue is only available for RGB lightbulbs"); value = math_1.clamp(value, 0, 360); return this.operateLight({ hue: value, }, transitionTime); } /** * Changes this lightbulb's color saturation * @returns true if a request was sent, false otherwise */ setSaturation(value, transitionTime) { this.ensureLink(); if (this.spectrum !== "rgb") throw new Error("setSaturation is only available for RGB lightbulbs"); value = math_1.clamp(value, 0, 100); return this.operateLight({ saturation: value, }, transitionTime); } /** Turns this object into JSON while leaving out the potential circular reference */ toJSON() { return { onOff: this.onOff, whenPowerRestored: this.whenPowerRestored, dimmer: this.dimmer, color: this.color, colorTemperature: this.colorTemperature, colorX: this.colorX, colorY: this.colorY, hue: this.hue, isDimmable: this.isDimmable, isSwitchable: this.isSwitchable, spectrum: this.spectrum, saturation: this.saturation, transitionTime: this.transitionTime, }; } /** * Fixes property values that are known to be bugged */ fixBuggedProperties() { super.fixBuggedProperties(); // For some reason the gateway reports lights with brightness 1 after turning off if (this.onOff === false && this.dimmer === MIN_BRIGHTNESS) this.dimmer = 0; // Some lights might only report color in CIE xy if (this.colorX !== undefined && this.colorY !== undefined && this.colorTemperature === undefined && !this.hasOwnProperty('hue')) { const rgb = conversions_1.conversions.rgbFromCIExyY(this.colorX / predefined_colors_1.MAX_COLOR, this.colorY / predefined_colors_1.MAX_COLOR); const hsv = conversions_1.conversions.rgbToHSV(rgb.r, rgb.g, rgb.b); this.color = conversions_1.conversions.rgbToString(rgb.r, rgb.g, rgb.b); this.hue = hsv.h; this.saturation = hsv.v; } return this; } } __decorate([ ipsoObject_1.doNotSerialize, __metadata("design:type", Object) ], Light.prototype, "_modelName", void 0); __decorate([ ipsoObject_1.doNotSerialize, __metadata("design:type", Object) ], Light.prototype, "_accessory", void 0); __decorate([ ipsoObject_1.ipsoKey("5706"), ipsoObject_1.doNotSerialize // this is done through hue/saturation , __metadata("design:type", String) ], Light.prototype, "color", void 0); __decorate([ ipsoObject_1.ipsoKey("5707"), ipsoObject_1.serializeWith(conversions_1.serializers.hue), ipsoObject_1.deserializeWith(conversions_1.deserializers.hue), ipsoObject_1.required((me, ref) => ref != null && me.saturation !== ref.saturation) // force hue to be present if saturation is , __metadata("design:type", Number) ], Light.prototype, "hue", void 0); __decorate([ ipsoObject_1.ipsoKey("5708"), ipsoObject_1.serializeWith(conversions_1.serializers.saturation), ipsoObject_1.deserializeWith(conversions_1.deserializers.saturation), ipsoObject_1.required((me, ref) => ref != null && me.hue !== ref.hue) // force saturation to be present if hue is , __metadata("design:type", Number) ], Light.prototype, "saturation", void 0); __decorate([ ipsoObject_1.ipsoKey("5709"), __metadata("design:type", Number) ], Light.prototype, "colorX", void 0); __decorate([ ipsoObject_1.ipsoKey("5710"), ipsoObject_1.required((me, ref) => ref != null && me.colorX !== ref.colorX) // force colorY to be present if colorX is , __metadata("design:type", Number) ], Light.prototype, "colorY", void 0); __decorate([ ipsoObject_1.ipsoKey("5711"), ipsoObject_1.serializeWith(conversions_1.serializers.colorTemperature), ipsoObject_1.deserializeWith(conversions_1.deserializers.colorTemperature), __metadata("design:type", Number) ], Light.prototype, "colorTemperature", void 0); __decorate([ ipsoObject_1.ipsoKey("5717"), __metadata("design:type", Object) ], Light.prototype, "UNKNOWN1", void 0); __decorate([ ipsoObject_1.ipsoKey("5712"), ipsoObject_1.required(), ipsoObject_1.serializeWith(conversions_1.serializers.transitionTime, { neverSkip: true }), ipsoObject_1.deserializeWith(conversions_1.deserializers.transitionTime, { neverSkip: true }), __metadata("design:type", Number) ], Light.prototype, "transitionTime", void 0); __decorate([ ipsoObject_1.ipsoKey("5805"), __metadata("design:type", Number) ], Light.prototype, "cumulativeActivePower", void 0); __decorate([ ipsoObject_1.ipsoKey("5851"), ipsoObject_1.serializeWith(conversions_1.serializers.brightness), ipsoObject_1.deserializeWith(conversions_1.deserializers.brightness), __metadata("design:type", Number) ], Light.prototype, "dimmer", void 0); __decorate([ ipsoObject_1.ipsoKey("5850"), __metadata("design:type", Boolean) ], Light.prototype, "onOff", void 0); __decorate([ ipsoObject_1.ipsoKey("5849"), __metadata("design:type", Number) ], Light.prototype, "whenPowerRestored", void 0); __decorate([ ipsoObject_1.ipsoKey("5852"), __metadata("design:type", Number) ], Light.prototype, "onTime", void 0); __decorate([ ipsoObject_1.ipsoKey("5820"), __metadata("design:type", Number) ], Light.prototype, "powerFactor", void 0); __decorate([ ipsoObject_1.ipsoKey("5701"), __metadata("design:type", String) ], Light.prototype, "unit", void 0); exports.Light = Light; // remember the minimum possible non-zero brightness to fix the bugged properties; const MIN_BRIGHTNESS = conversions_1.deserializers.brightness(1); const rgbRegex = /^[0-9A-Fa-f]{6}$/; /** * Creates a proxy for an RGB lamp, * which converts RGB color to Hue / Saturation */ function createRGBProxy(raw = false) { function get(me, key) { switch (key) { case "color": { if (typeof me.color === "string" && rgbRegex.test(me.color)) { // predefined color, return it return me.color; } else { // calculate it from hue/saturation const { r, g, b } = conversions_1.conversions.rgbFromHSV(me.hue, me.saturation / 100, 1); return conversions_1.conversions.rgbToString(r, g, b); } } default: return me[key]; } } function set(me, key, value) { switch (key) { case "color": { if (predefined_colors_1.predefinedColors.has(value)) { // its a predefined color, use the predefined values const definition = predefined_colors_1.predefinedColors.get(value); // we checked with `has` if (raw) { me.hue = definition.hue_raw; me.saturation = definition.saturation_raw; } else { me.hue = definition.hue; me.saturation = definition.saturation; } } else { // only accept HEX colors if (rgbRegex.test(value)) { // calculate the X/Y values const { r, g, b } = conversions_1.conversions.rgbFromString(value); const { h, s /* ignore v */ } = conversions_1.conversions.rgbToHSV(r, g, b); if (raw) { me.hue = Math.round(h / 360 * predefined_colors_1.MAX_COLOR); me.saturation = Math.round(s * predefined_colors_1.MAX_COLOR); } else { me.hue = h; me.saturation = s * 100; } } } break; } default: me[key] = value; } return true; } return { get, set }; } /** * Creates a proxy for an RGB lamp, * which converts RGB color to CIE xy */ function createRGBCIExyProxy() { function get(me, key) { switch (key) { case "color": { if (typeof me.color === "string" && rgbRegex.test(me.color)) { // predefined color, return it return me.color; } else { // calculate it from colorX/Y const { r, g, b } = conversions_1.conversions.rgbFromCIExyY(me.colorX / predefined_colors_1.MAX_COLOR, me.colorY / predefined_colors_1.MAX_COLOR); return conversions_1.conversions.rgbToString(r, g, b); } } case "hue": { const { r, g, b } = conversions_1.conversions.rgbFromString(get(me, "color")); const { h } = conversions_1.conversions.rgbToHSV(r, g, b); return h; } case "saturation": { const { r, g, b } = conversions_1.conversions.rgbFromString(get(me, "color")); const { s } = conversions_1.conversions.rgbToHSV(r, g, b); return Math.round(s * 100); } default: return me[key]; } } function set(me, key, value, receiver) { switch (key) { case "color": { if (predefined_colors_1.predefinedColors.has(value)) { // its a predefined color, use the predefined values const definition = predefined_colors_1.predefinedColors.get(value); // we checked with `has` me.colorX = definition.colorX; me.colorY = definition.colorY; } else { // only accept HEX colors if (rgbRegex.test(value)) { // calculate the X/Y values const { r, g, b } = conversions_1.conversions.rgbFromString(value); const { x, y } = conversions_1.conversions.rgbToCIExyY(r, g, b); me.colorX = Math.round(x * predefined_colors_1.MAX_COLOR); me.colorY = Math.round(y * predefined_colors_1.MAX_COLOR); } } break; } case "hue": { let { r, g, b } = conversions_1.conversions.rgbFromString(get(me, "color")); // tslint:disable-next-line:prefer-const let { h, s, v } = conversions_1.conversions.rgbToHSV(r, g, b); h = value; ({ r, g, b } = conversions_1.conversions.rgbFromHSV(h, s, v)); set(me, "color", conversions_1.conversions.rgbToString(r, g, b), receiver); break; } case "saturation": { let { r, g, b } = conversions_1.conversions.rgbFromString(get(me, "color")); // tslint:disable-next-line:prefer-const let { h, s, v } = conversions_1.conversions.rgbToHSV(r, g, b); s = value / 100; ({ r, g, b } = conversions_1.conversions.rgbFromHSV(h, s, v)); set(me, "color", conversions_1.conversions.rgbToString(r, g, b), receiver); break; } default: me[key] = value; } return true; } return { get, set }; }