@hoobs/hue
Version:
HOOBS plugin for Philips Hue and deCONZ
1,666 lines (1,600 loc) • 51.2 kB
JavaScript
// homebridge-hue/lib/HueLight.js
// Copyright © 2016-2020 Erik Baauw. All rights reserved.
//
// Homebridge plugin for Philips Hue and/or deCONZ.
//
// HueLight provides support for Philips Hue lights and groups.
'use strict'
const moment = require('moment')
const homebridgeLib = require('homebridge-lib')
module.exports = {
setHomebridge: setHomebridge,
HueLight: HueLight
}
const formatError = homebridgeLib.CommandLineTool.formatError
// Safe default gamut taking into account:
// - The maximum value for CurrentX and CurrentY, 65279 (0xfeff), as defined
// by the ZCL spec;
// - A potential division by zero error for CurrentY, when translating the
// xy values back to hue/sat.
const defaultGamut = {
r: [0.9961, 0.0001],
g: [0, 0.9961],
b: [0, 0.0001]
}
const knownLights = {
'Busch-Jaeger': {
// See: https://www.busch-jaeger.de/en/products/product-solutions/dimmer/busch-radio-controlled-dimmer-zigbee-light-link/
models: {
RM01: { // 6715 U-500 with 6736-84
fix: function () { // Issue #241
if (this.config.bri && this.obj.type === 'On/Off light') {
this.log.debug(
'%s: %s: ignoring state.bri for %s', this.bridge.name,
this.resource, this.obj.type
)
this.config.bri = false
}
}
}
}
},
'dresden elektronik': {
// See: https://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/wireless-ballasts/?L=1
},
FeiBit: {
models: {
'FNB56-SKT1EHG1.2': {
fix: function () { // issue #361
this.obj.type = 'On/Off plug-in unit'
}
}
}
},
'Feibit Inc co.': { // Issue #171
},
GLEDOPTO: {
// See: https://www.led-trading.de/zigbee-kompatibel-controller-led-lichtsteuerung
gamut: {
r: [0.7006, 0.2993],
g: [0.1387, 0.8148],
b: [0.1510, 0.0227]
},
models: {
'GL-C-008': { noWaitUpdate: true },
GLEDOPTO: { // Issue #244
fix: function () {
if (
this.subtype === '0a' &&
this.obj.type === 'Dimmable light' &&
this.version === '1.0.2'
) {
this.model = 'RGBW'
} else if (
this.subtype === '0b' &&
this.obj.type === 'Color temperature light' &&
this.version === '1.3.002'
) {
this.model = 'WW/CW'
} else if (
this.subtype === '0b' &&
this.obj.type === 'Extended color light' &&
this.version === '1.0.2'
) {
this.model = 'RGB+CCT'
if (this.accessory.resources.lights.other.length > 1) {
this.model = 'RGBW'
this.config.ct = false
}
} else {
this.log.warn(
'%s: %s: unknown light model %j', this.bridge.name,
this.resource, this.obj
)
return
}
this.log.debug(
'%s: %s: set model to %s', this.bridge.name,
this.resource, this.model
)
}
}
}
},
'IKEA of Sweden': {
// See: http://www.ikea.com/us/en/catalog/categories/departments/lighting/smart_lighting/
},
innr: {
// See: https://shop.innrlighting.com/en/shop
gamut: { // Issue #152
r: [0.8817, 0.1033],
g: [0.2204, 0.7758],
b: [0.0551, 0.1940]
},
models: {
'DL 110': { noAlert: true }, // Spot
'FL 110': { noAlert: true }, // Flex Light
'PL 110': { noAlert: true }, // Puck Light
'SL 110 M': { noAlert: true }, // Spot, issue #166
'SL 110 N': { noAlert: true }, // Spot, issue #166
'SL 110 W': { noAlert: true }, // Spot, issue #166
'SP 120': { fix: function () { this.config.bri = false } }, // smart plug
'ST 110': { noAlert: true }, // Strip
'UC 110': { noAlert: true } // Under Cabinet
}
},
MLI: { // Issue #439
gamut: {
r: [0.68, 0.31],
g: [0.11, 0.82],
b: [0.13, 0.04]
}
},
'Neuhaus Lighting Group ': { // Issue #455
},
OSRAM: {
gamut: {
r: [0.6877, 0.3161],
g: [0.1807, 0.7282],
b: [0.1246, 0.0580]
},
fix: function () {
if (this.obj.swversion === 'V1.03.07') {
this.config.noTransitionTime = true
}
}
},
Pee: { // Issue #217
},
Philips: {
// See: http://www.developers.meethue.com/documentation/supported-lights
gamuts: { // Color gamut per light model.
A: { // Color Lights
r: [0.7040, 0.2960],
g: [0.2151, 0.7106],
b: [0.1380, 0.0800]
},
B: { // Extended Color Lights
r: [0.6750, 0.3220],
g: [0.4090, 0.5180],
b: [0.1670, 0.0400]
},
C: { // next gen Extended Color Lights
r: [0.6920, 0.3080],
g: [0.1700, 0.7000],
b: [0.1530, 0.0480]
}
},
models: {
LLC001: { // Living Colors Gen1 Iris
fix: function () {
if (this.obj.uniqueid === 'ff:ff:ff:ff:ff:ff:ff:ff-0b') {
this.serialNumber = this.bridge.serialNumber + '-L' + this.id
}
this.config.xy = false
this.config.hs = true
},
noWallSwitch: true
},
LCT001: { gamut: 'B' }, // Hue bulb A19
LCT002: { gamut: 'B' }, // Hue Spot BR30
LCT003: { gamut: 'B' }, // Hue Spot GU10
LCT007: { gamut: 'B' }, // Hue bulb A19
LCT010: { gamut: 'C' }, // Hue bulb A19
LCT011: { gamut: 'C' }, // Hue BR30
LCT012: { gamut: 'C' }, // Hue Color Candle
LCT014: { gamut: 'C' }, // Hue bulb A19
LCT015: { gamut: 'C' }, // Hue bulb A19
LCT016: { gamut: 'C' }, // Hue bulb A19
LLC005: { gamut: 'A' }, // Living Colors Gen3 Bloom, Aura
LLC006: { gamut: 'A' }, // Living Colors Gen3 Iris
LLC007: { gamut: 'A' }, // Living Colors Gen3 Bloom, Aura
LLC010: { gamut: 'A' }, // Hue Living Colors Iris
LLC011: { gamut: 'A' }, // Hue Living Colors Bloom
LLC012: { gamut: 'A' }, // Hue Living Colors Bloom
LLC013: { gamut: 'A' }, // Disney Living Colors
LLC014: { gamut: 'A' }, // Living Colors Gen3 Bloom, Aura
LLC020: { gamut: 'C' }, // Hue Go
LLM001: { gamut: 'B' }, // Color Light Module
LST001: { gamut: 'A' }, // Hue LightStrips
LST002: { gamut: 'C' } // Hue LightStrips Plus
}
},
ShenZhen_Homa: { // PR #234, issue #235
}
}
knownLights['Signify Netherlands B.V'] = knownLights.Philips
// ===== Colour Conversion =====================================================
// Return point in color gamut closest to p.
function closestInGamut (p, gamut) {
// Return cross product of two points.
function crossProduct (p1, p2) {
return p1.x * p2.y - p1.y * p2.x
}
// Return distance between two points.
function distance (p1, p2) {
const dx = p1.x - p2.x
const dy = p1.y - p2.y
return Math.sqrt(dx * dx + dy * dy)
}
// Return point on line a,b closest to p.
function closest (a, b, p) {
const ap = { x: p.x - a.x, y: p.y - a.y }
const ab = { x: b.x - a.x, y: b.y - a.y }
let t = (ap.x * ab.x + ap.y * ab.y) / (ab.x * ab.x + ab.y * ab.y)
t = t < 0.0 ? 0.0 : t > 1.0 ? 1.0 : t
return { x: a.x + t * ab.x, y: a.y + t * ab.y }
}
const R = { x: gamut.r[0], y: gamut.r[1] }
const G = { x: gamut.g[0], y: gamut.g[1] }
const B = { x: gamut.b[0], y: gamut.b[1] }
const v1 = { x: G.x - R.x, y: G.y - R.y }
const v2 = { x: B.x - R.x, y: B.y - R.y }
const v = crossProduct(v1, v2)
const q = { x: p.x - R.x, y: p.y - R.y }
const s = crossProduct(q, v2) / v
const t = crossProduct(v1, q) / v
if (s >= 0.0 && t >= 0.0 && s + t <= 1.0) {
return p
}
const pRG = closest(R, G, p)
const pGB = closest(G, B, p)
const pBR = closest(B, R, p)
const dRG = distance(p, pRG)
const dGB = distance(p, pGB)
const dBR = distance(p, pBR)
let min = dRG
p = pRG
if (dGB < min) {
min = dGB
p = pGB
}
if (dBR < min) {
p = pBR
}
return p
}
// Transform bridge xy values [0.0000, 1.0000]
// to homekit hue value [0˚, 360˚] and saturation value [0%, 100%].
function hueSat (xy, gamut) {
// Inverse Gamma correction (sRGB Companding).
function compand (v) {
return v <= 0.0031308
? 12.92 * v : (1.0 + 0.055) * Math.pow(v, (1.0 / 2.4)) - 0.055
}
// Correction for negative values is missing from Philips' documentation.
function correctNegative () {
const m = Math.min(R, G, B)
if (m < 0.0) {
R -= m
G -= m
B -= m
}
}
function rescale () {
const M = Math.max(R, G, B)
if (M > 1.0) {
R /= M
G /= M
B /= M
}
}
// xyY to XYZ to RGB
// See: https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/
const p = closestInGamut({ x: xy[0], y: xy[1] }, gamut)
const x = p.x
const y = p.y === 0.0 ? 0.000001 : p.y
const z = 1.0 - x - y
const Y = 1.0
const X = (Y / y) * x
const Z = (Y / y) * z
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
correctNegative()
rescale()
R = compand(R)
G = compand(G)
B = compand(B)
rescale()
// RGB to HSV
// See: https://en.wikipedia.org/wiki/HSL_and_HSV
const M = Math.max(R, G, B)
const m = Math.min(R, G, B)
const C = M - m
const S = (M === 0.0) ? 0.0 : C / M
let H
switch (M) {
case m:
H = 0.0
break
case R:
H = (G - B) / C
if (H < 0) {
H += 6.0
}
break
case G:
H = (B - R) / C
H += 2.0
break
case B:
H = (R - G) / C
H += 4.0
break
}
return { hue: Math.round(H * 60.0), sat: Math.round(S * 100.0) }
}
// Transform homekit hue value [0˚, 360˚] and saturation value [0%, 100%]
// to bridge xy values [0.0, 1.0].
function invHueSat (hue, sat, gamut) {
// Gamma correction (inverse sRGB Companding).
function invCompand (v) {
return v > 0.04045 ? Math.pow((v + 0.055) / (1.0 + 0.055), 2.4) : v / 12.92
}
// HSV to RGB
// See: https://en.wikipedia.org/wiki/HSL_and_HSV
let H = hue / 360.0
const S = sat / 100.0
const V = 1
const C = V * S
H *= 6
const m = V - C
let x = (H % 2) - 1.0
if (x < 0) {
x = -x
}
x = C * (1.0 - x)
let R, G, B
switch (Math.floor(H) % 6) {
case 0: R = C + m; G = x + m; B = m; break
case 1: R = x + m; G = C + m; B = m; break
case 2: R = m; G = C + m; B = x + m; break
case 3: R = m; G = x + m; B = C + m; break
case 4: R = x + m; G = m; B = C + m; break
case 5: R = C + m; G = m; B = x + m; break
}
// RGB to XYZ to xyY
// See: http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
const linearR = invCompand(R)
const linearG = invCompand(G)
const linearB = invCompand(B)
const X = linearR * 0.664511 + linearG * 0.154324 + linearB * 0.162028
const Y = linearR * 0.283881 + linearG * 0.668433 + linearB * 0.047685
const Z = linearR * 0.000088 + linearG * 0.072310 + linearB * 0.986039
const sum = X + Y + Z
const p = sum === 0.0 ? { x: 0.0, y: 0.0 } : { x: X / sum, y: Y / sum }
const q = closestInGamut(p, gamut)
return [Math.round(q.x * 10000) / 10000, Math.round(q.y * 10000) / 10000]
}
// ===== Homebridge ============================================================
let Service
let Characteristic
let my
function setHomebridge (homebridge, _my, _eve) {
Service = homebridge.hap.Service
Characteristic = homebridge.hap.Characteristic
my = _my
}
// ===== HueLight ==============================================================
function HueLight (accessory, id, obj, type = 'light') {
this.accessory = accessory
this.id = id
this.obj = obj
this.type = type
this.log = this.accessory.log
this.serialNumber = this.accessory.serialNumber
this.bridge = this.accessory.bridge
this.name = obj.name
this.resource = '/' + this.type + 's/' + this.id
this.key = this.type === 'group' ? 'action' : 'state'
this.resourcePath = this.resource + '/' + this.key
this.desiredState = {}
this.deferrals = []
for (const key in this.obj.action) {
if (key !== 'on') {
this.obj.state[key] = this.obj.action[key]
}
}
this.hk = {}
this.setConfig()
this.infoService = this.accessory.getInfoService(this)
if (this.config.windowCovering) {
this.service = new Service.WindowCovering(this.name, this.subtype)
this.service.getCharacteristic(Characteristic.TargetPosition)
.on('set', this.setPosition.bind(this))
.setProps({ minStep: 5 })
this.checkPosition(this.obj.state.bri)
this.service.getCharacteristic(Characteristic.HoldPosition)
.on('set', this.setHoldPosition.bind(this))
.setValue(false)
if (this.config.sat) {
this.service.getCharacteristic(Characteristic.TargetHorizontalTiltAngle)
.on('set', this.setTilt.bind(this))
.setProps({ minStep: 5 })
this.checkTilt(this.obj.state.sat)
}
} else if (!this.config.on) { // Warning device
this.service = new Service.Outlet(this.name, this.subtype)
this.service.getCharacteristic(Characteristic.OutletInUse)
.updateValue(1)
this.service.getCharacteristic(Characteristic.On)
.on('set', this.setWarning.bind(this))
this.checkOn(this.obj.state.on)
this.hk.duration = 0
this.service.addOptionalCharacteristic(Characteristic.SetDuration)
this.service.getCharacteristic(Characteristic.SetDuration)
.updateValue(this.hk.duration)
.on('set', this.setDuration.bind(this))
this.hk.mute = 0
this.service.addOptionalCharacteristic(Characteristic.Mute)
this.service.getCharacteristic(Characteristic.Mute)
.updateValue(this.hk.mute)
.on('set', this.setMute.bind(this))
} else {
if (this.config.outlet) {
this.service = new Service.Outlet(this.name, this.subtype)
this.service.getCharacteristic(Characteristic.OutletInUse)
.updateValue(1)
} else if (this.config.valve) {
this.service = new Service.Valve(this.name, this.subtype)
this.service.getCharacteristic(Characteristic.InUse)
.updateValue(0)
this.service.getCharacteristic(Characteristic.ValveType)
.updateValue(Characteristic.ValveType.GENERIC_VALVE)
} else {
this.service = new Service.Lightbulb(this.name, this.subtype)
}
if (this.config.valve) {
this.service.getCharacteristic(Characteristic.Active)
.on('set', this.setActive.bind(this))
this.checkActive(this.obj.state.on)
this.hk.duration = 0
this.service.getCharacteristic(Characteristic.SetDuration)
.updateValue(this.hk.duration)
.on('set', this.setDuration.bind(this))
this.hk.autoInActive = 0
this.service.getCharacteristic(Characteristic.RemainingDuration)
.updateValue(0)
.on('get', this.getRemainingDuration.bind(this))
} else {
this.service.getCharacteristic(Characteristic.On)
.on('set', this.setOn.bind(this))
if (this.type === 'group') {
if (this.bridge.platform.config.anyOn) {
this.anyOnKey = 'any_on'
this.AnyOnCharacteristic = my.Characteristics.AnyOn
this.service.addOptionalCharacteristic(this.AnyOnCharacteristic)
this.service.getCharacteristic(this.AnyOnCharacteristic)
.on('set', this.setAnyOn.bind(this))
this.checkAllOn(this.obj.state.all_on)
} else {
this.anyOnKey = 'on'
this.AnyOnCharacteristic = Characteristic.On
this.checkAllOn = () => {}
}
this.checkAnyOn(this.obj.state.any_on)
if (this.config.streaming) {
this.service.addOptionalCharacteristic(my.Characteristics.Streaming)
this.service.getCharacteristic(my.Characteristics.Streaming)
.on('set', this.setStreaming.bind(this))
this.checkStreaming(this.obj.stream.active)
}
} else {
this.checkOn(this.obj.state.on)
}
}
if (this.config.bri) {
this.service.getCharacteristic(Characteristic.Brightness)
.on('set', this.setBri.bind(this))
this.checkBri(this.obj.state.bri)
this.service.addOptionalCharacteristic(my.Characteristics.BrightnessChange)
this.service.getCharacteristic(my.Characteristics.BrightnessChange)
.updateValue(0)
.on('set', this.setBriChange.bind(this))
}
if (this.config.ct) {
this.service.addOptionalCharacteristic(Characteristic.ColorTemperature)
this.service.getCharacteristic(Characteristic.ColorTemperature)
.on('set', this.setCT.bind(this))
.setProps({
minValue: this.config.minCT,
maxValue: this.config.maxCT
})
this.checkCT(this.obj.state.ct)
}
if (this.config.xy || this.config.hs) {
this.service.getCharacteristic(Characteristic.Hue)
.on('set', this.setHue.bind(this))
this.service.getCharacteristic(Characteristic.Saturation)
.on('set', this.setSat.bind(this))
if (this.config.xy) {
this.checkXY(this.obj.state.xy)
} else {
this.checkHue(this.obj.state.hue)
this.checkSat(this.obj.state.sat)
}
}
if (this.config.colorloop) {
this.service.addOptionalCharacteristic(my.Characteristics.ColorLoop)
this.service.getCharacteristic(my.Characteristics.ColorLoop)
.on('set', this.setColorLoop.bind(this))
}
}
if (this.type === 'light') {
this.service.addOptionalCharacteristic(Characteristic.StatusFault)
this.checkReachable(this.obj.state.reachable)
if (this.bridge.config.nativeHomeKitLights) {
this.service.addOptionalCharacteristic(my.Characteristics.UniqueID)
this.service.getCharacteristic(my.Characteristics.UniqueID)
.updateValue(this.obj.uniqueid)
}
}
if (this.bridge.platform.config.resource) {
this.service.addOptionalCharacteristic(my.Characteristics.Resource)
this.service.getCharacteristic(my.Characteristics.Resource)
.updateValue(this.resource)
}
}
// Store configuration to this.config.
HueLight.prototype.setConfig = function () {
for (const key in this.obj.action) {
if (key !== 'on') {
this.obj.state[key] = this.obj.action[key]
}
}
this.config = {
on: this.obj.state.on !== undefined || this.obj.state.any_on !== undefined,
bri: this.obj.state.bri !== undefined,
ct: this.obj.state.ct !== undefined,
xy: this.obj.state.xy !== undefined,
colorloop: this.obj.state.effect !== undefined,
wallSwitch: false,
outlet: this.bridge.outlet[this.type + 's'][this.id],
resetTimeout: this.bridge.platform.config.resetTimeout,
valve: this.type === 'light' && this.bridge.valve[this.id],
waitTimeUpdate: this.bridge.platform.config.waitTimeUpdate
}
if (this.config.xy && !this.bridge.platform.config.forceCt) {
this.config.ct = false
}
if (this.config.outlet || this.config.valve) {
this.config.bri = false
this.config.ct = false
this.config.xy = false
this.config.windowCovering = false
} else if (this.obj.type === 'Window covering device') {
this.config.windowCovering = true
this.config.sat = this.obj.state.sat !== undefined
this.config.ct = false
this.config.xy = false
} else if (this.type === 'light') {
this.config.wallSwitch = this.bridge.platform.config.wallSwitch ||
this.bridge.wallswitch[this.id]
}
if (this.config.ct) {
// Default colour temperature range: 153 (~6500K) - 500 (2000K).
this.config.minCT = 153
this.config.maxCT = 500
}
if (this.config.xy) {
this.config.gamut = defaultGamut
}
if (this.type === 'group') {
this.manufacturer = this.bridge.obj.manufacturername
this.model = this.obj.type
if (this.accessory.isMulti) {
this.subtype = 'G' + this.id
}
this.version = this.bridge.version
if (this.obj.type === 'Entertainment') {
this.config.streaming = true
}
this.log.debug('%s: %s: config: %j', this.bridge.name, this.resource, this.config)
return
}
this.manufacturer = this.obj.manufacturername
this.model = this.obj.modelid
if (this.accessory.isMulti) {
this.subtype = 'L' + this.id
} else {
this.subtype = this.obj.uniqueid.split('-')[1]
}
this.version = this.obj.swversion
this.config.unknown = knownLights[this.obj.manufacturername] == null
const manufacturer = knownLights[this.obj.manufacturername] || {}
manufacturer.models = manufacturer.models || {}
const model = manufacturer.models[this.obj.modelid] || {}
if (typeof model.fix === 'function') {
model.fix.call(this)
} else if (typeof manufacturer.fix === 'function') {
manufacturer.fix.call(this)
}
if (this.config.ct) {
if (model.minCT != null) {
// whitelisted model
this.config.minCT = model.minCT
} else if (manufacturer.minCT != null) {
// whitelisted manufacturer default
this.config.minCT = manufacturer.minCT
} else if (this.obj.ctmin != null && this.obj.ctmin !== 0) {
// reported by deCONZ
this.config.minCT = this.obj.ctmin
} else if (
this.obj.capabilities != null && this.obj.capabilities.control != null &&
this.obj.capabilities.control.ct != null &&
this.obj.capabilities.control.ct !== 0
) {
// reported by Hue bridge
this.config.minCT = this.obj.capabilities.control.ct.min
}
if (model.maxCT != null) {
// whitelisted model
this.config.maxCT = model.maxCT
} else if (manufacturer.maxCT != null) {
// whitelisted manufacturer default
this.config.maxCT = manufacturer.maxCT
} else if (
this.obj.ctmax != null &&
this.obj.ctmax !== 0 && this.obj.ctmax !== 65535
) {
// reported by deCONZ
this.config.maxCT = this.obj.ctmax
} else if (
this.obj.capabilities != null && this.obj.capabilities.control != null &&
this.obj.capabilities.control.ct != null &&
this.obj.capabilities.control.ct !== 0 &&
this.obj.capabilities.control.ct !== 65535
) {
// reported by Hue bridge
this.config.maxCT = this.obj.capabilities.control.ct.max
} else if (this.config.unknown) {
this.log.warn(
'%s: %s: warning: using default colour temperature range for unknown light model %j',
this.bridge.name, this.resource, this.obj
)
}
}
if (this.config.xy) {
if (
model.gamut != null && manufacturer.gamuts != null &&
manufacturer.gamuts[model.gamut] != null
) {
// whitelisted model
this.config.gamut = manufacturer.gamuts[model.gamut]
} else if (manufacturer.gamut != null) {
// whitelisted manufacturer default
this.config.gamut = manufacturer.gamut
} else if (
this.obj.capabilities != null && this.obj.capabilities.control != null &&
this.obj.capabilities.control.colorgamut != null
) {
// reported by Hue bridge
const gamut = this.obj.capabilities.control.colorgamut
this.config.gamut = {
r: [
Math.min(gamut[0][0], defaultGamut.r[0]),
Math.max(gamut[0][1], defaultGamut.r[1])
],
g: [
Math.max(gamut[1][0], defaultGamut.g[0]),
Math.min(gamut[1][1], defaultGamut.g[1])
],
b: [
Math.max(gamut[2][0], defaultGamut.b[0]),
Math.max(gamut[2][1], defaultGamut.b[1])
]
}
} else if (this.config.unknown) {
this.log.warn(
'%s: %s: warning: using default colour gamut for unknown light model %j',
this.bridge.name, this.resource, this.obj
)
}
}
if (model.noAlert) {
this.config.noAlert = true
}
if (model.noWallSwitch) {
this.config.wallSwitch = false
}
if (model.noWaitUpdate) {
this.config.waitTimeUpdate = 0
}
this.log.debug('%s: %s: config: %j', this.bridge.name, this.resource, this.config)
}
// ===== Bridge Events =========================================================
HueLight.prototype.heartbeat = function (beat, obj) {
if (this.updating) {
return
}
for (const key in obj.action) {
if (key !== 'on') {
obj.state[key] = obj.action[key]
}
}
this.checkState(obj.state)
if (this.config.streaming) {
this.checkStreaming(obj.stream.active)
}
}
HueLight.prototype.checkState = function (state, event) {
for (const key in state) {
switch (key) {
case 'alert':
break
// jshint -W106
case 'all_on':
this.checkAllOn(state.all_on)
break
case 'any_on':
this.checkAnyOn(state.any_on)
break
// jshint +W106
case 'bri':
if (this.config.windowCovering) {
this.checkPosition(state.bri)
break
}
this.checkBri(state.bri)
break
case 'colormode':
this.obj.state.colormode = state.colormode
break
case 'ct':
this.checkCT(state.ct)
break
case 'effect':
this.checkEffect(state.effect)
break
case 'hue':
this.checkHue(state.hue)
break
case 'mode':
break
case 'on':
if (this.config.valve) {
this.checkActive(state.on)
} else {
this.checkOn(state.on)
}
break
case 'reachable':
this.checkReachable(state.reachable)
break
case 'sat':
if (this.config.windowCovering) {
this.checkTilt(state.sat)
break
}
this.checkSat(state.sat)
break
case 'scene':
break
case 'x':
break
case 'xy':
this.checkXY(state.xy)
break
case 'y':
break
default:
this.log.debug(
'%s: ignore unknown attribute state.%s', this.name, key
)
break
}
}
}
HueLight.prototype.checkOn = function (on) {
if (this.config.windowCovering) {
// handle state.bri only as state.on is derived from state.bri
return
}
if (this.obj.state.on !== on) {
this.log.debug(
'%s: %s on changed from %s to %s', this.name, this.type,
this.obj.state.on, on
)
this.obj.state.on = on
}
let hkOn
if (this.config.wallSwitch && this.obj.state.reachable !== true) {
if (this.hk.on) {
this.log.info('%s: not reachable: force homekit on to 0', this.name)
}
hkOn = 0
} else {
hkOn = this.obj.state.on ? 1 : 0
}
if (this.hk.on !== hkOn) {
if (this.hk.on !== undefined) {
this.log.info(
'%s: set homekit on from %s to %s', this.name,
this.hk.on, hkOn
)
}
this.hk.on = hkOn
this.service.getCharacteristic(Characteristic.On)
.updateValue(this.hk.on)
}
}
HueLight.prototype.checkAllOn = function (allOn) {
// jshint -W106
if (this.obj.state.all_on !== allOn) {
this.log.debug(
'%s: %s all_on changed from %s to %s', this.name, this.type,
this.obj.state.all_on, allOn
)
this.obj.state.all_on = allOn
}
const hkOn = this.obj.state.all_on ? 1 : 0
if (this.hk.on !== hkOn) {
if (this.hk.on !== undefined) {
this.log.info(
'%s: set homekit on from %s to %s', this.name,
this.hk.on, hkOn
)
}
this.hk.on = hkOn
this.service.getCharacteristic(Characteristic.On)
.updateValue(this.hk.on)
}
}
HueLight.prototype.checkAnyOn = function (anyOn) {
// jshint -W106
if (this.obj.state[this.anyOnKey] !== anyOn) {
this.log.debug(
'%s: %s any_on changed from %s to %s', this.name, this.type,
this.obj.state[this.anyOnKey], anyOn
)
this.obj.state[this.anyOnKey] = anyOn
}
const hkAnyOn = this.obj.state[this.anyOnKey] ? 1 : 0
if (this.hk[this.anyOnKey] !== hkAnyOn) {
if (this.hk[this.anyOnKey] !== undefined) {
this.log.info(
'%s: set homekit any on from %s to %s', this.name,
this.hk[this.anyOnKey], hkAnyOn
)
}
this.hk[this.anyOnKey] = hkAnyOn
this.service.getCharacteristic(this.AnyOnCharacteristic)
.updateValue(this.hk[this.anyOnKey])
}
}
HueLight.prototype.checkStreaming = function (streaming) {
if (this.obj.state.streaming !== streaming) {
this.log.debug(
'%s: streaming changed from %s to %s', this.name,
this.obj.state.streaming, streaming
)
this.obj.state.streaming = streaming
}
const hkStreaming = this.obj.state.streaming ? 1 : 0
if (this.hk.streaming !== hkStreaming) {
if (this.hk.streaming !== undefined) {
this.log.info(
'%s: set homekit streaming from %s to %s', this.name,
this.hk.streaming, hkStreaming
)
}
this.hk.streaming = hkStreaming
this.service.getCharacteristic(my.Characteristics.Streaming)
.updateValue(this.hk.streaming)
}
}
HueLight.prototype.checkActive = function (on) {
if (this.obj.state.on !== on) {
this.log.debug(
'%s: %s active changed from %s to %s', this.name, this.type,
this.obj.state.on, on
)
this.obj.state.on = on
}
const hkActive = this.obj.state.on ? 1 : 0
if (this.hk.active !== hkActive) {
if (this.hk.active !== undefined) {
this.log.info(
'%s: set homekit active from %s to %s', this.name,
this.hk.active, hkActive
)
}
this.hk.active = hkActive
this.service.getCharacteristic(Characteristic.Active)
.updateValue(this.hk.active)
this.didSetActive()
}
}
HueLight.prototype.checkBri = function (bri) {
if (!this.config.bri) {
return
}
if (this.obj.state.bri !== bri) {
this.log.debug(
'%s: %s bri changed from %s to %s', this.name, this.type,
this.obj.state.bri, bri
)
this.obj.state.bri = bri
}
const hkBri = Math.round(this.obj.state.bri * 100.0 / 254.0)
if (this.hk.bri !== hkBri) {
if (this.hk.bri !== undefined) {
this.log.info(
'%s: set homekit brightness from %s%% to %s%%', this.name,
this.hk.bri, hkBri
)
}
this.hk.bri = hkBri
this.service.getCharacteristic(Characteristic.Brightness)
.updateValue(this.hk.bri)
}
}
HueLight.prototype.checkCT = function (ct) {
if (!this.config.ct) {
return
}
if (this.obj.state.ct !== ct) {
if (this.obj.state.colormode === 'ct') {
this.log.debug(
'%s: %s ct changed from %s to %s', this.name, this.type,
this.obj.state.ct, ct
)
} else {
this.log.debug(
'%s: %s ct updated by %s from %s to %s', this.name, this.type,
this.obj.state.colormode, this.obj.state.ct, ct
)
}
if (this.recentlyUpdated) {
this.log.debug('%s: recently updated - ignore changed ct', this.name)
return
}
this.obj.state.ct = ct
}
const hkCT = Math.max(this.config.minCT, Math.min(this.config.maxCT, ct))
if (this.hk.ct !== hkCT) {
if (this.hk.ct !== undefined) {
this.log.info(
'%s: set homekit color temperature from %s mired to %s mired',
this.name, this.hk.ct, hkCT
)
}
this.hk.ct = hkCT
this.service.getCharacteristic(Characteristic.ColorTemperature)
.updateValue(this.hk.ct)
}
}
HueLight.prototype.checkHue = function (hue) {
if (!this.config.hs) {
return
}
if (this.obj.state.hue !== hue) {
if (this.obj.state.colormode === 'hs') {
this.log.debug(
'%s: %s hue changed from %s to %s', this.name, this.type,
this.obj.state.hue, hue
)
} else {
this.log.debug(
'%s: %s hue changed by %s from %s to %s', this.name, this.type,
this.obj.state.colormode, this.obj.state.hue, hue
)
}
this.obj.state.hue = hue
}
const hkHue = Math.round(this.obj.state.hue * 360.0 / 65535.0)
if (this.hk.hue !== hkHue) {
if (this.hk.hue !== undefined) {
this.log.info(
'%s: set homekit hue from %s˚ to %s˚', this.name,
this.hk.hue, hkHue
)
}
this.hk.hue = hkHue
this.service.getCharacteristic(Characteristic.Hue)
.updateValue(this.hk.hue)
}
}
HueLight.prototype.checkSat = function (sat) {
if (!this.config.hs) {
return
}
if (this.obj.state.sat !== sat) {
if (this.obj.state.colormode === 'hs') {
this.log.debug(
'%s: %s sat changed from %s to %s', this.name, this.type,
this.obj.state.sat, sat
)
} else {
this.log.debug(
'%s: %s sat changed by %s from %s to %s', this.name, this.type,
this.obj.state.colormode, this.obj.state.sat, sat
)
}
this.obj.state.sat = sat
}
const hkSat = Math.round(this.obj.state.sat * 100.0 / 254.0)
if (this.hk.sat !== hkSat) {
if (this.hk.sat !== undefined) {
this.log.info(
'%s: set homekit sat from %s%%to %s%%', this.name,
this.hk.sat, hkSat
)
}
this.hk.sat = hkSat
this.service.getCharacteristic(Characteristic.Saturation)
.updateValue(this.hk.sat)
}
}
HueLight.prototype.checkEffect = function (effect) {
if (!this.config.colorloop) {
return
}
if (this.obj.state.effect !== effect) {
this.log.debug(
'%s: %s effect changed from %s to %s', this.name, this.type,
this.obj.state.effect, effect
)
this.obj.state.effect = effect
}
const hkColorloop = effect === 'colorloop'
if (this.hk.colorloop !== hkColorloop) {
if (this.hk.colorloop !== undefined) {
this.log.info(
'%s: set homekit colorloop from %s to %s', this.name,
this.hk.colorloop, hkColorloop
)
}
this.hk.colorloop = hkColorloop
this.service.getCharacteristic(my.Characteristics.ColorLoop)
.updateValue(this.hk.colorloop)
}
}
HueLight.prototype.checkReachable = function (reachable) {
if (this.obj.state.reachable !== reachable) {
this.log.debug(
'%s: %s reachable changed from %s to %s', this.name, this.type,
this.obj.state.reachable, reachable
)
this.obj.state.reachable = reachable
}
const hkFault = this.obj.state.reachable ? 0 : 1
if (this.hk.fault !== hkFault) {
if (this.hk.fault !== undefined) {
this.log.info(
'%s: set homekit status fault from %s to %s', this.name,
this.hk.fault, hkFault
)
}
this.hk.fault = hkFault
this.service.getCharacteristic(Characteristic.StatusFault)
.updateValue(this.hk.fault)
if (this.config.wallSwitch && !this.config.valve) {
this.checkOn(this.obj.state.on)
}
}
}
HueLight.prototype.checkXY = function (xy) {
if (!this.config.xy) {
return
}
if (this.obj.state.xy[0] !== xy[0] || this.obj.state.xy[1] !== xy[1]) {
if (this.obj.state.colormode === 'xy') {
this.log.debug(
'%s: %s xy changed from %j to %j', this.name, this.type,
this.obj.state.xy, xy
)
} else {
this.log.debug(
'%s: %s xy changed by %s from %j to %j', this.name, this.type,
this.obj.state.colormode, this.obj.state.xy, xy
)
}
if (this.recentlyUpdated) {
this.log.debug('%s: recently updated - ignore changed xy', this.name)
return
}
this.obj.state.xy = xy
}
const hs = hueSat(this.obj.state.xy, this.config.gamut)
const hkHue = hs.hue
const hkSat = hs.sat
if (this.hk.hue !== hkHue) {
if (this.hk.hue !== undefined) {
this.log.info(
'%s: set homekit hue from %s˚ to %s˚', this.name, this.hk.hue, hkHue
)
}
this.hk.hue = hkHue
this.service.getCharacteristic(Characteristic.Hue)
.updateValue(this.hk.hue)
}
if (this.hk.sat !== hkSat) {
if (this.hk.sat !== undefined) {
this.log.info(
'%s: set homekit saturation from %s%% to %s%%', this.name,
this.hk.sat, hkSat
)
}
this.hk.sat = hkSat
this.service.getCharacteristic(Characteristic.Saturation)
.updateValue(this.hk.sat)
}
}
HueLight.prototype.checkPosition = function (bri) {
if (!this.config.windowCovering || !this.config.bri) {
return
}
if (this.obj.state.bri !== bri) {
this.log.debug(
'%s: %s bri (position) changed from %s to %s', this.name, this.type,
this.obj.state.bri, bri
)
this.obj.state.bri = bri
}
let hkPosition = 100 - Math.round(this.obj.state.bri * 100.0 / 254.0)
hkPosition = 5 * Math.round(hkPosition / 5.0) // round to multiple of 5
if (this.hk.currentPosition !== hkPosition) {
if (this.hk.currentPosition !== undefined) {
this.log.info(
'%s: set homekit current position from %s%% to %s%%', this.name,
this.hk.currentPosition, hkPosition
)
}
this.hk.currentPosition = hkPosition
this.service.getCharacteristic(Characteristic.CurrentPosition)
.updateValue(this.hk.currentPosition)
this.service.getCharacteristic(Characteristic.PositionState)
.updateValue(Characteristic.PositionState.STOPPED)
this.hk.targetPosition = hkPosition
this.service.getCharacteristic(Characteristic.TargetPosition)
.updateValue(this.hk.targetPosition)
}
}
HueLight.prototype.checkTilt = function (sat) {
if (!this.config.windowCovering || !this.config.sat) {
return
}
if (this.obj.state.sat !== sat) {
this.log.debug(
'%s: %s sat (tilt) changed from %s to %s', this.name, this.type,
this.obj.state.sat, sat
)
this.obj.state.sat = sat
}
let hkTilt = Math.round(this.obj.state.sat * 180.0 / 254.0) - 90
hkTilt = 5 * Math.round(hkTilt / 5.0) // round to multiple of 5
if (this.hk.currentTilt !== hkTilt) {
if (this.hk.currentTilt !== undefined) {
this.log.info(
'%s: set homekit current tilt from %s%% to %s%%', this.name,
this.hk.currentTilt, hkTilt
)
}
this.hk.currentTilt = hkTilt
this.service.getCharacteristic(Characteristic.CurrentHorizontalTiltAngle)
.updateValue(this.hk.currentTilt)
this.hk.targetTilt = hkTilt
this.service.getCharacteristic(Characteristic.TargetHorizontalTiltAngle)
.updateValue(this.hk.targetTilt)
}
}
// ===== Homekit Events ========================================================
HueLight.prototype.identify = function (callback) {
this.log.debug('%s: %s: config: %j', this.bridge.name, this.resource, this.config)
this.log.info('%s: identify', this.name)
if (this.config.valve) {
return callback()
}
let alert = 'select'
let stop
if (this.bridge.type === 'bridge') {
if (this.config.noAlert) {
return callback()
}
} else if (this.manufacturer === this.bridge.philips) {
alert = 'breathe'
stop = 'stop'
} else if (!this.config.on) {
alert = 'blink'
stop = 'none'
}
this.put({ alert: alert }).then((obj) => {
if (stop != null) {
setTimeout(() => {
this.put({ alert: stop })
}, 1500)
}
return callback()
}).catch((error) => {
return callback(error)
})
}
HueLight.prototype.setWarning = function (on, callback) {
on = on ? 1 : 0
if (on === this.hk.on) {
return callback()
}
this.log.info(
'%s: homekit on changed from %s to %s', this.name, this.hk.on, on
)
const onTime = this.hk.duration > 0 ? this.hk.duration : 1
let body = { alert: 'none' }
if (on) {
if (this.hk.mute) {
body = { alert: 'blink', ontime: onTime }
} else if (this.hk.duration === 0) {
body = { alert: 'select' }
} else {
body = { alert: 'lselect', ontime: onTime }
}
}
this.put(body).then((obj) => {
setTimeout(() => {
if (this.hk.on === 1) {
this.log.info('%s: set homekit on from 1 to 0', this.name)
this.hk.on = 0
this.service.updateCharacteristic(Characteristic.On, this.hk.on)
}
}, onTime * 1000)
this.hk.on = on
return callback()
}).catch((error) => {
return callback(error)
})
}
HueLight.prototype.setMute = function (mute, callback) {
mute = mute ? 1 : 0
if (mute === this.hk.mute) {
return callback()
}
this.log.info(
'%s: homekit mute changed from %s to %s', this.name, this.hk.mute, mute
)
this.hk.mute = mute
return callback()
}
HueLight.prototype.setOn = function (on, callback) {
on = on ? 1 : 0
if (on && this.config.wallSwitch && this.obj.state.reachable !== true) {
return callback(new Error('unreachable'))
}
if (on === this.hk.on) {
return callback()
}
this.log.info(
'%s: homekit on changed from %s to %s', this.name, this.hk.on, on
)
const oldOn = this.hk.on
this.hk.on = on
const newOn = !!this.hk.on
const request = { on: newOn }
if (this.config.noTransitionTime && !newOn) {
request.transitiontime = 0
}
this.put(request).then(() => {
if (this.type === 'group') {
// jshint -W106
this.obj.state[this.anyOnKey] = newOn
this.obj.state.all_on = newOn
// jshint +W106
} else {
this.obj.state.on = newOn
}
callback()
}).catch((error) => {
this.hk.on = oldOn
callback(error)
})
}
HueLight.prototype.setAnyOn = function (anyOn, callback) {
anyOn = anyOn ? 1 : 0
if (anyOn === this.hk[this.anyOnKey]) {
return callback()
}
this.log.info(
'%s: homekit any on changed from %s to %s', this.name,
this.hk[this.anyOnKey], anyOn
)
const oldAnyOn = this.hk[this.anyOnKey]
this.hk[this.anyOnKey] = anyOn
const newOn = !!this.hk[this.anyOnKey]
this.put({ on: newOn }).then(() => {
// jshint -W106
this.obj.state[this.anyOnKey] = newOn
this.obj.state.all_on = newOn
// jshint +W106
callback()
}).catch((error) => {
this.hk[this.anyOnKey] = oldAnyOn
callback(error)
})
}
HueLight.prototype.setStreaming = function (streaming, callback) {
streaming = streaming ? 1 : 0
if (streaming === this.hk.streaming) {
return callback()
}
this.log.info(
'%s: homekit streaming changed from %s to %s', this.name,
this.hk.streaming, streaming
)
this.hk.streaming = streaming
if (this.hk.streaming === 0) {
this.put({ stream: { active: false } }).then((obj) => {
this.obj.state.streaming = false
return callback()
}).catch((error) => {
return callback(error)
})
} else {
callback()
}
}
HueLight.prototype.setActive = function (active, callback) {
if (active === this.hk.active) {
return callback()
}
this.log.info(
'%s: homekit active changed from %s to %s', this.name, this.hk.active, active
)
const oldActive = this.hk.active
this.hk.active = active
const newOn = this.hk.active === 1
this.put({ on: newOn }).then(() => {
this.obj.state.on = newOn
callback()
this.didSetActive()
}).catch((error) => {
this.hk.active = oldActive
callback(error)
})
}
HueLight.prototype.setBri = function (bri, callback) {
if (bri === this.hk.bri) {
return callback()
}
this.log.info(
'%s: homekit brightness changed from %s%% to %s%%', this.name,
this.hk.bri, bri
)
const oldBri = this.hk.bri
this.hk.bri = bri
const newBri = Math.round(this.hk.bri * 254.0 / 100.0)
this.put({ bri: newBri }).then(() => {
this.obj.state.bri = newBri
callback()
}).catch((error) => {
this.hk.bri = oldBri
callback(error)
})
}
HueLight.prototype.setBriChange = function (delta, callback) {
if (delta === 0) {
return callback()
}
this.log.info(
'%s: homekit brightness change by %s%%', this.name,
delta
)
const briDelta = Math.round(delta * 254.0 / 100.0)
this.put({ bri_inc: briDelta }).then((obj) => {
setTimeout(() => {
this.service.setCharacteristic(my.Characteristics.BrightnessChange, 0)
}, this.config.resetTimeout)
callback()
}).catch((error) => {
callback(error)
})
}
HueLight.prototype.setCT = function (ct, callback) {
if (ct === this.hk.ct) {
return callback()
}
this.log.info(
'%s: homekit color temperature changed from %s mired to %s mired',
this.name, this.hk.ct, ct
)
const oldCT = this.hk.ct
this.hk.ct = ct
const newCT = this.hk.ct
this.put({ ct: newCT }).then(() => {
this.obj.state.ct = newCT
callback()
}).catch((error) => {
this.hk.ct = oldCT
callback(error)
})
}
HueLight.prototype.setDuration = function (duration, callback) {
if (duration === this.hk.duration) {
return callback()
}
this.log.info(
'%s: homekit duration changed from %ss to %ss',
this.name, this.hk.duration, duration
)
this.hk.duration = duration
callback()
}
HueLight.prototype.setHue = function (hue, callback) {
if (hue === this.hk.hue) {
return callback()
}
this.log.info(
'%s: homekit hue changed from %s˚ to %s˚', this.name, this.hk.hue, hue
)
const oldHue = this.hk.hue
this.hk.hue = hue
if (this.config.xy) {
const newXY = invHueSat(this.hk.hue, this.hk.sat, this.config.gamut)
this.put({ xy: newXY }).then(() => {
this.obj.state.xy = newXY
callback()
}).catch((error) => {
this.hk.hue = oldHue
callback(error)
})
} else {
const newHue = Math.round(this.hk.hue * 65535.0 / 360.0)
this.put({ hue: newHue }).then(() => {
this.obj.state.hue = newHue
callback()
}).catch((error) => {
this.hk.hue = oldHue
callback(error)
})
}
}
HueLight.prototype.setSat = function (sat, callback) {
if (sat === this.hk.sat) {
return callback()
}
this.log.info(
'%s: homekit saturation changed from %s%% to %s%%', this.name,
this.hk.sat, sat
)
const oldSat = this.hk.sat
this.hk.sat = sat
if (this.config.xy) {
const newXY = invHueSat(this.hk.hue, this.hk.sat, this.config.gamut)
this.put({ xy: newXY }).then(() => {
this.obj.state.xy = newXY
callback()
}).catch((error) => {
this.hk.sat = oldSat
callback(error)
})
} else {
const newSat = Math.round(this.hk.sat * 254.0 / 100.0)
this.put({ sat: newSat }).then(() => {
this.obj.state.sat = newSat
callback()
}).catch((error) => {
this.hk.sat = oldSat
callback(error)
})
}
}
HueLight.prototype.setColorLoop = function (colorloop, callback) {
if (colorloop === this.hk.colorloop) {
return callback()
}
this.log.info(
'%s: homekit colorloop changed from %s to %s', this.name,
this.hk.colorloop, colorloop
)
const oldColorloop = this.hk.colorloop
this.hk.colorloop = colorloop
const newEffect = this.hk.colorloop ? 'colorloop' : 'none'
this.put({ effect: newEffect }).then(() => {
this.obj.state.effect = newEffect
callback()
}).catch((error) => {
this.hk.colorloop = oldColorloop
callback(error)
})
}
HueLight.prototype.setPosition = function (position, callback) {
if (position === this.hk.targetPosition) {
return callback()
}
this.log.info(
'%s: homekit target position changed from %s%% to %s%%', this.name,
this.hk.targetPosition, position
)
const oldPosition = this.hk.targetPosition
this.hk.targetPosition = position
const newBri = Math.round((100 - this.hk.targetPosition) * 254.0 / 100.0)
this.put({ bri: newBri }).then(() => {
this.obj.state.bri = newBri
const positionState = this.hk.targetPosition > this.hk.currentPosition
? Characteristic.PositionState.INCREASING
: Characteristic.PositionState.DECREASING
this.service.getCharacteristic(Characteristic.PositionState)
.updateValue(positionState)
callback()
}).catch((error) => {
this.hk.targetPosition = oldPosition
callback(error)
})
}
HueLight.prototype.setHoldPosition = function (hold, callback) {
if (!hold) {
return callback()
}
this.log.info('%s: homekit hold position', this.name)
this.put({ bri_inc: 0 }).then((obj) => {
callback()
}).catch((error) => {
callback(error)
})
}
HueLight.prototype.setTilt = function (tilt, callback) {
if (tilt === this.hk.targetTilt) {
return callback()
}
this.log.info(
'%s: homekit target tilt changed from %s° to %s°', this.name,
this.hk.targetTilt, tilt
)
const oldTilt = this.hk.targetTilt
this.hk.targetTilt = tilt
const newSat = Math.round((90 + this.hk.targetTilt) * 254.0 / 180.0)
this.put({ sat: newSat }).then(() => {
this.obj.state.sat = newSat
callback()
}).catch((error) => {
this.hk.targetTilt = oldTilt
callback(error)
})
}
HueLight.prototype.getRemainingDuration = function (callback) {
let remaining = this.hk.autoInActive - moment().unix()
remaining = remaining > 0 ? remaining : 0
this.log.info('%s: remaining duration %ss', this.name, remaining)
callback(null, remaining)
}
HueLight.prototype.didSetActive = function () {
if (this.hk.duration > 0) {
if (this.hk.active) {
this.hk.autoInActive = moment().unix() + this.hk.duration
this.hk.autoInActiveTimeout = setTimeout(() => {
this.log.debug('%s: remaining duration 0s', this.name)
this.put({ on: false }).then(() => {
}).catch((error) => {
this.log.error('%s: error %j', this.name, formatError(error))
})
}, this.hk.duration * 1000)
} else {
if (this.hk.autoInActiveTimeout != null) {
clearTimeout(this.hk.autoInActiveTimeout)
delete this.hk.autoInActiveTimeout
}
}
}
setTimeout(() => {
this.log.info('%s: set homekit in use to %s', this.name, this.hk.active)
this.service.updateCharacteristic(Characteristic.InUse, this.hk.active)
if (this.hk.active && this.hk.duration > 0) {
this.log.info(
'%s: set homekit remaining duration to %ss', this.name, this.hk.duration
)
this.service.updateCharacteristic(
Characteristic.RemainingDuration, this.hk.duration
)
} else if (this.hk.autoInActive !== 0) {
this.hk.autoInActive = 0
this.log.info('%s: set homekit remaining duration to 0s', this.name)
this.service.updateCharacteristic(Characteristic.RemainingDuration, 0)
}
}, 500)
}
// Collect changes into a combined request.
HueLight.prototype.put = function (state) {
return new Promise((resolve, reject) => {
for (const key in state) {
this.desiredState[key] = state[key]
}
const d = {