From 3ea1ec4bd090d715b40ef57697c056fe49c5ea33 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 14 Nov 2020 13:00:15 +0100 Subject: [PATCH] Update HueLight.js - Changes to handling colour temperature, see #814. - First step towards implemening adaptive lighting, for now only for Hue LCT015. --- lib/HueLight.js | 137 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 12 deletions(-) diff --git a/lib/HueLight.js b/lib/HueLight.js index b05e195c..2e1caeba 100644 --- a/lib/HueLight.js +++ b/lib/HueLight.js @@ -197,7 +197,7 @@ const knownLights = { LCT011: { gamut: 'C' }, // Hue BR30 LCT012: { gamut: 'C' }, // Hue Color Candle LCT014: { gamut: 'C' }, // Hue bulb A19 - LCT015: { gamut: 'C' }, // Hue bulb A19 + LCT015: { gamut: 'C', adaptiveLighting: true }, // Hue bulb A19 LCT016: { gamut: 'C' }, // Hue bulb A19 LLC005: { gamut: 'A' }, // Living Colors Gen3 Bloom, Aura LLC006: { gamut: 'A' }, // Living Colors Gen3 Iris @@ -217,7 +217,7 @@ const knownLights = { } } -knownLights['Signify Netherlands B.V'] = knownLights.Philips +knownLights['Signify Netherlands B.V.'] = knownLights.Philips // ===== Colour Conversion ===================================================== @@ -396,6 +396,48 @@ function invHueSat (hue, sat, gamut) { return [Math.round(q.x * 10000) / 10000, Math.round(q.y * 10000) / 10000] } +// ct to xy +// From deCONZ REST API plugin. +// Results don't match xy values as retuned by Hue LCT015 exactly, but seem +// to be close enough. +function invCt (ct) { + const kelvin = 1000000 / ct + let x, y + + if (kelvin < 4000) { + x = 11790 + + 57520658 / kelvin + + -15358885888 / kelvin / kelvin + + -17440695910400 / kelvin / kelvin / kelvin + } else { + x = 15754 + + 14590587 / kelvin + + 138086835814 / kelvin / kelvin + + -198301902438400 / kelvin / kelvin / kelvin + } + if (kelvin < 2222) { + y = -3312 + + 35808 * x / 0x10000 + + -22087 * x * x / 0x100000000 + + -18126 * x * x * x / 0x1000000000000 + } else if (kelvin < 4000) { + y = -2744 + + 34265 * x / 0x10000 + + -22514 * x * x / 0x100000000 + + -15645 * x * x * x / 0x1000000000000 + } else { + y = -6062 + + 61458 * x / 0x10000 + + -96229 * x * x / 0x100000000 + + 50491 * x * x * x / 0x1000000000000 + } + y *= 4 + x /= 0xFFFF + y /= 0xFFFF + + return [Math.round(x * 10000) / 10000, Math.round(y * 10000) / 10000] +} + function dateToString (date, utc = true) { if (date == null || date === 'none') { return 'n/a' @@ -411,13 +453,11 @@ function dateToString (date, utc = true) { let Service let Characteristic let my -let eve function setHomebridge (homebridge, _my, _eve) { Service = homebridge.hap.Service Characteristic = homebridge.hap.Characteristic my = _my - eve = _eve } // ===== HueLight ============================================================== @@ -547,11 +587,9 @@ function HueLight (accessory, id, obj, type = 'light') { .on('set', this.setBriChange.bind(this)) } if (this.config.ct) { - this.colorTemperatureCharacteristic = this.config.xy - ? eve.Characteristics.ColorTemperature - : Characteristic.ColorTemperature - this.service.addOptionalCharacteristic(this.colorTemperatureCharacteristic) - this.service.getCharacteristic(this.colorTemperatureCharacteristic) + this.colorTemperatureCharacteristic = Characteristic.ColorTemperature + this.service.addOptionalCharacteristic(Characteristic.ColorTemperature) + this.service.getCharacteristic(Characteristic.ColorTemperature) .on('set', this.setCT.bind(this)) .setProps({ minValue: this.config.minCT, @@ -576,6 +614,18 @@ function HueLight (accessory, id, obj, type = 'light') { this.service.getCharacteristic(my.Characteristics.ColorLoop) .on('set', this.setColorLoop.bind(this)) } + if (this.config.adaptiveLighting) { + this.service.addOptionalCharacteristic(my.Characteristics.SupportedTransitionConfiguration) + this.service.getCharacteristic(my.Characteristics.SupportedTransitionConfiguration) + .on('get', this.getSupportedTransitionConfiguration.bind(this)) + this.service.addOptionalCharacteristic(my.Characteristics.TransitionControl) + this.service.getCharacteristic(my.Characteristics.TransitionControl) + .on('get', this.getTransitionControl.bind(this)) + .on('set', this.setTransitionControl.bind(this)) + this.service.addOptionalCharacteristic(my.Characteristics.ActiveTransitionCount) + this.service.getCharacteristic(my.Characteristics.ActiveTransitionCount) + .updateValue(0) + } } if (this.type === 'light') { this.service.addOptionalCharacteristic(Characteristic.StatusFault) @@ -773,6 +823,9 @@ HueLight.prototype.setConfig = function () { if (model.noWaitUpdate) { this.config.waitTimeUpdate = 0 } + if (model.adaptiveLighting) { + this.config.adaptiveLighting = true + } this.log.debug('%s: %s: config: %j', this.bridge.name, this.resource, this.config) } @@ -1071,7 +1124,7 @@ HueLight.prototype.checkCT = function (ct) { ) } this.hk.ct = hkCT - this.service.getCharacteristic(this.colorTemperatureCharacteristic) + this.service.getCharacteristic(Characteristic.ColorTemperature) .updateValue(this.hk.ct) } } @@ -1190,7 +1243,7 @@ HueLight.prototype.checkReachable = function (reachable) { } } -HueLight.prototype.checkXY = function (xy) { +HueLight.prototype.checkXY = function (xy, fromCt = false) { if (!this.config.xy) { return } @@ -1206,7 +1259,7 @@ HueLight.prototype.checkXY = function (xy) { this.obj.state.colormode, this.obj.state.xy, xy ) } - if (this.recentlyUpdated) { + if (this.recentlyUpdated && !fromCt) { this.log.debug('%s: recently updated - ignore changed xy', this.name) return } @@ -1660,6 +1713,7 @@ HueLight.prototype.setCT = function (ct, callback) { const newCT = this.hk.ct this.put({ ct: newCT }).then(() => { this.obj.state.ct = newCT + this.checkXY(invCt(this.obj.state.ct), true) callback() }).catch((error) => { this.hk.ct = oldCT @@ -1883,6 +1937,65 @@ HueLight.prototype.didSetActive = function () { }, 500) } +HueLight.prototype.getSupportedTransitionConfiguration = function (callback) { + // The SuppotedTransitionConfiguration value is constant, but the iid values + // for the characteristics are only assigned when the HAP server is started, + // so we cannot set the value while defining the service. + const bri = this.service.getCharacteristic(Characteristic.Brightness).iid + const ct = this.service.getCharacteristic(Characteristic.ColorTemperature).iid + const buf = Buffer.alloc(38) + let i = 0 + + buf[i++] = 1 // T0 + buf[i++] = 16 // L0 + buf[i++] = 1 // V0 T0 + buf[i++] = 8 // V0 L0 + buf.writeUint32LE(bri, i) // V0 V0 + i += 8 + buf[i++] = 2 // V0 T1 + buf[i++] = 4 // V0 L1 + buf.writeUint16LE(1, i) // V0 V1 + i += 4 + + buf[i++] = 0 // T1 + buf[i++] = 0 // L1 + + buf[i++] = 1 // T2 + buf[i++] = 16 // L2 + buf[i++] = 1 // V2 T0 + buf[i++] = 8 // V2 L0 + buf.writeUint32LE(ct, i) // V2 V0 + i += 8 + buf[i++] = 2 // V2 T1 + buf[i++] = 4 // V2 L1 + buf.writeUint16LE(2, i) // V2 V1 + i += 4 + + this.log.debug( + '%s: brightness idd: %d, color temperature idd: %d', this.name, bri, ct + ) + this.log.debug( + '%s: supported transition configuration: %s', this.name, + buf.toString('base64') + ) + callback(null, buf.toString('base64')) + + // Remove the event handler, since we now have the value. + this.service.getCharacteristic(my.Characteristics.SupportedTransitionConfiguration) + .setValue(buf.toString('base64')) + .removeAllListeners('get') +} + +HueLight.prototype.getTransitionControl = function (callback) { + this.log.warn('get control') + callback(null, Buffer.alloc(0).toString('base64')) +} + +HueLight.prototype.setTransitionControl = function (control, callback) { + this.log.warn('set control to: %j', control) + callback() +} + // Collect changes into a combined request. HueLight.prototype.put = function (state) { return new Promise((resolve, reject) => {