UNPKG

homebridge-yeelighter

Version:

Yeelight support for Homebridge with particular support of ceiling lights

329 lines 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LightService = exports.EMPTY_ATTRIBUTES = exports.POWERMODE_MOON = exports.POWERMODE_HSV = exports.POWERMODE_CT = exports.POWERMODE_DEFAULT = void 0; exports.powerModeFromColorModeAndActiveMode = powerModeFromColorModeAndActiveMode; exports.convertColorTemperature = convertColorTemperature; exports.isValidValue = isValidValue; const colortools_1 = require("./colortools"); exports.POWERMODE_DEFAULT = 0; exports.POWERMODE_CT = 1; exports.POWERMODE_HSV = 3; exports.POWERMODE_MOON = 5; exports.EMPTY_ATTRIBUTES = { power: false, color_mode: 0, bright: 0, hue: 0, sat: 0, ct: 0, bg_power: false, bg_bright: 0, bg_hue: 0, bg_sat: 0, bg_ct: 0, bg_lmode: 0, nl_br: 0, active_mode: 0, name: "unknown" }; function powerModeFromColorModeAndActiveMode(color_mode, active_mode) { // PowerMode: // 0: Normal turn on operation(default value) // 1: Turn on and switch to CT mode. (used for white lights) // 2: Turn on and switch to RGB mode. (never used here) // 3: Turn on and switch to HSV mode. (used for color lights) // 4: Turn on and switch to color flow mode. // 5: Turn on and switch to Night light mode. (Ceiling light only). // ColorMode: // 1 means color mode, (rgb -- never used here) // 2 means color temperature mode, (CT used for white light) // 3 means HSV mode (used for color lights) if (active_mode === 1) { return exports.POWERMODE_MOON; } switch (color_mode) { case 1: { // this is never used return exports.POWERMODE_DEFAULT; } case 2: { return exports.POWERMODE_CT; } case 3: { return exports.POWERMODE_HSV; } default: { // this should never happen! return exports.POWERMODE_DEFAULT; } } } /** * Converts a color temperature in Kelvin to a mired value for Homebridge. * The mired value is the reciprocal of the color temperature in microreciprocal degrees. * The formula is: mired = 1,000,000 / kelvin. * Therefore it can also be used for reversed: kelvin = 1,000,000 / mired. * * @param kelvin - The color temperature in Kelvin (e.g., 2000K to 6500K). * @returns The corresponding value in mireds (HomeKit-compatible scale). */ function convertColorTemperature(kelvin) { // check if value is valid if (Number.isFinite(kelvin) && kelvin > 0) { return Math.min(Math.round(1000000 / kelvin), 370); } // This will cause a warning in the logs return 1000; } function isValidValue(value) { switch (typeof value) { case "boolean": { return true; } case "number": { return Number.isFinite(value); } case "string": { return true; } default: { return false; } } } /** * Base class for services representing a light. This is a mixin class that is * used by the concrete light services. */ class LightService { constructor(parameters, subtype) { var _a; this.subtype = subtype; this.debounceTimers = {}; this.log = (message, ...optionalParameters) => { this.platform.log.info(`${this.logPrefix} ${message}`, optionalParameters); }; this.warn = (message, ...optionalParameters) => { this.platform.log.warn(`${this.logPrefix} ${message}`, optionalParameters); }; this.error = (message, ...optionalParameters) => { this.platform.log.error(`${this.logPrefix} ${message}`, optionalParameters); }; this.debug = (message, ...optionalParameters) => { if (this.light.detailedLogging) { this.platform.log.info(`${this.logPrefix} ${message}`, optionalParameters); } else { this.platform.log.debug(`${this.logPrefix} ${message}`, optionalParameters); } }; this.onPowerOff = () => { this.updateCharacteristic(this.platform.Characteristic.On, false); }; this.platform = parameters.platform; this.accessory = parameters.accessory; this.light = parameters.light; // we use powerMode to store the currently set mode switch (this.light.info.color_mode) { case 2: { this.powerMode = exports.POWERMODE_CT; break; } case 3: { this.powerMode = exports.POWERMODE_HSV; break; } default: { // this should never happen! this.powerMode = exports.POWERMODE_DEFAULT; break; } } this.name = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.name) || this.device.info.id; // get the LightBulb service if it exists, otherwise create a new LightBulb service // we create multiple services for lights that have a subtype set if (subtype) { const subtypeUid = `${this.light.info.id}#${subtype}`; this.log(`registering subtype ${subtypeUid}`); this.service = this.accessory.getService(subtypeUid) || this.accessory.addService(this.platform.Service.Lightbulb, `${this.name} ${subtype}`, subtypeUid); } else { this.log(`no subtype`); this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb); } // name handling this.service.getCharacteristic(this.platform.Characteristic.ConfiguredName).on("set", (value, callback) => { this.log("setConfiguredName", value); const name = value.toString(); this.service.displayName = name; this.name = name; this.service.setCharacteristic(this.platform.Characteristic.Name, value); this.platform.api.updatePlatformAccessories([this.accessory]); callback(); }); } cancelAllDebounces() { for (const key in this.debounceTimers) { clearTimeout(this.debounceTimers[key]); delete this.debounceTimers[key]; } } get detailedLogging() { return this.light.detailedLogging; } updateName(value) { this.debug("Ignoring updateName", value); // this.name = value; // this.service.setCharacteristic(this.platform.Characteristic.Name, `${value} ${this.subtype}`); // this.platform.api.updatePlatformAccessories([this.accessory]); } get device() { return this.light.device; } get logPrefix() { if (this.subtype) { return `[${this.name}#${this.subtype}]`; } return `[${this.name}]`; } get config() { const override = (this.platform.config.override || []); const { info } = this.device; const overrideConfig = override.find((item) => item.id === info.id); return overrideConfig || { id: info.id }; } get specs() { return this.light.specs; } async attributes() { return this.light.getAttributes(); } async getAttribute(attribute) { // this should never throw const a = await this.attributes(); return a[attribute]; } setAttributes(attributes) { this.light.setAttributes(attributes); } handleCharacteristic(uuid, getter, setter) { const characteristic = this.service.getCharacteristic(uuid); if (!characteristic) { throw new Error("Could not get Characteristic"); } characteristic.on("get", async (callback) => { if (this.light.connected) { callback(undefined, await getter()); } else { callback(new Error("light disconnected")); } }); characteristic.on("set", async (value, callback) => { if (this.light.connected && isValidValue(value)) { await setter(value); callback(); } else { this.log(`failed to set to value`, value, this.light.connected); callback(new Error("light disconnected or invalid value")); } }); return characteristic; } async updateCharacteristic(uuid, value) { const characteristic = this.service.getCharacteristic(uuid); if (!characteristic) { throw undefined; } if (isValidValue(value)) { characteristic.updateValue(value); } else { this.error("updateCharacteristic value is not finite", value); } } async sendCommandPromiseWithErrorHandling(method, parameters) { try { await this.light.sendCommandPromise(method, parameters); } catch (error) { // catch all errors so Homebridge doesn't crash this.warn("Command failed", error); } } async sendCommand(method, parameters) { return this.sendCommandPromiseWithErrorHandling(method, parameters); } async sendSuddenCommand(method, parameter) { return this.sendCommandPromiseWithErrorHandling(method, [parameter, "sudden", 0]); } async sendSmoothCommand(method, parameter) { return this.sendCommandPromiseWithErrorHandling(method, [parameter, "smooth", 500]); } async sendAnimatedCommand(method, parameters) { var _a, _b, _c; const messageParameters = Array.isArray(parameters) ? parameters : [parameters]; if ((_a = this.platform.config) === null || _a === void 0 ? void 0 : _a.animateChanges) { const animationTime = (_c = (_b = this.platform.config) === null || _b === void 0 ? void 0 : _b.animationTime) !== null && _c !== void 0 ? _c : 500; if (this.debounceTimers[method]) { clearTimeout(this.debounceTimers[method]); } this.debounceTimers[method] = setTimeout(async () => { await this.sendCommandPromiseWithErrorHandling(method, [...messageParameters, "smooth", animationTime]); delete this.debounceTimers[method]; }, animationTime); return; } else { return this.sendCommandPromiseWithErrorHandling(method, [...messageParameters, "sudden", 0]); } } saveDefaultIfNeeded() { var _a; if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.saveDefault) { this.sendCommand("set_default", []); } } async ensurePowerMode(mode, prefix = "") { if (this.powerMode !== mode) { await this.sendCommand(`${prefix}set_power`, ["on", "sudden", 0, mode]); this.powerMode = mode; if (prefix == "bg_") { this.setAttributes({ bg_power: true }); } else { this.setAttributes({ power: true, active_mode: mode == exports.POWERMODE_MOON ? 1 : 0 }); } } } async setHSV(prefix = "") { const hue = this.lastHue; const sat = this.lastSat; if (hue && sat) { await this.ensurePowerMode(exports.POWERMODE_HSV, prefix); const hsv = [hue, sat]; delete this.lastHue; delete this.lastSat; await this.sendAnimatedCommand(`${prefix}set_hsv`, hsv); if (prefix == "bg_") { this.setAttributes({ bg_hue: hue, bg_sat: sat }); } else { this.setAttributes({ hue, sat }); } this.saveDefaultIfNeeded(); } } updateColorFromCT(value) { const { h, s } = (0, colortools_1.convertHomeKitColorTemperatureToHomeKitColor)(value); this.service.getCharacteristic(this.platform.Characteristic.Hue).updateValue(h); this.service.getCharacteristic(this.platform.Characteristic.Saturation).updateValue(s); } } exports.LightService = LightService; //# sourceMappingURL=lightservice.js.map