homebridge-deconz 0.0.9 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -6
  2. package/cli/deconz.js +12 -0
  3. package/config.schema.json +2 -1
  4. package/homebridge-ui/public/index.html +26 -0
  5. package/homebridge-ui/public/style.css +0 -0
  6. package/homebridge-ui/server.js +43 -0
  7. package/lib/Deconz/ApiClient.js +34 -1
  8. package/lib/Deconz/ApiResponse.js +1 -1
  9. package/lib/Deconz/Device.js +0 -7
  10. package/lib/Deconz/Resource.js +752 -27
  11. package/lib/DeconzAccessory/Contact.js +54 -0
  12. package/lib/DeconzAccessory/Gateway.js +102 -66
  13. package/lib/DeconzAccessory/Light.js +42 -35
  14. package/lib/DeconzAccessory/Motion.js +51 -0
  15. package/lib/DeconzAccessory/Sensor.js +35 -0
  16. package/lib/DeconzAccessory/Temperature.js +63 -0
  17. package/lib/DeconzAccessory/Thermostat.js +50 -0
  18. package/lib/DeconzAccessory/WarningDevice.js +56 -0
  19. package/lib/DeconzAccessory/WindowCovering.js +47 -0
  20. package/lib/DeconzAccessory/index.js +145 -4
  21. package/lib/DeconzPlatform.js +6 -3
  22. package/lib/DeconzService/AirPressure.js +43 -0
  23. package/lib/DeconzService/AirQuality.js +20 -11
  24. package/lib/DeconzService/Alarm.js +13 -9
  25. package/lib/DeconzService/Battery.js +53 -0
  26. package/lib/DeconzService/Button.js +9 -2
  27. package/lib/DeconzService/CarbonMonoxide.js +38 -0
  28. package/lib/DeconzService/Consumption.js +65 -0
  29. package/lib/DeconzService/Contact.js +60 -0
  30. package/lib/DeconzService/Daylight.js +132 -0
  31. package/lib/DeconzService/DeviceSettings.js +12 -4
  32. package/lib/DeconzService/Flag.js +52 -0
  33. package/lib/DeconzService/GatewaySettings.js +1 -4
  34. package/lib/DeconzService/Humidity.js +37 -0
  35. package/lib/DeconzService/Leak.js +38 -0
  36. package/lib/DeconzService/Light.js +289 -280
  37. package/lib/DeconzService/LightLevel.js +54 -0
  38. package/lib/DeconzService/LightsResource.js +112 -0
  39. package/lib/DeconzService/Motion.js +101 -0
  40. package/lib/DeconzService/Outlet.js +76 -0
  41. package/lib/DeconzService/Power.js +83 -0
  42. package/lib/DeconzService/SensorsResource.js +96 -0
  43. package/lib/DeconzService/Smoke.js +38 -0
  44. package/lib/DeconzService/Status.js +53 -0
  45. package/lib/DeconzService/Switch.js +93 -0
  46. package/lib/DeconzService/Temperature.js +63 -0
  47. package/lib/DeconzService/Thermostat.js +175 -0
  48. package/lib/DeconzService/WarningDevice.js +68 -0
  49. package/lib/DeconzService/WindowCovering.js +139 -0
  50. package/lib/DeconzService/index.js +79 -25
  51. package/package.json +6 -5
  52. package/lib/Client/ApiError.js +0 -42
  53. package/lib/DeconzAccessory/Device.js +0 -69
  54. package/lib/DeconzService/Sensor.js +0 -61
@@ -1,4 +1,4 @@
1
- // homebridge-deconz/lib/DeconzService/Sensor.js
1
+ // homebridge-deconz/lib/DeconzService/Light.js
2
2
  // Copyright © 2022 Erik Baauw. All rights reserved.
3
3
  //
4
4
  // Homebridge plugin for deCONZ.
@@ -6,54 +6,39 @@
6
6
  'use strict'
7
7
 
8
8
  const homebridgeLib = require('homebridge-lib')
9
- const Deconz = require('../Deconz')
9
+ const DeconzService = require('../DeconzService')
10
10
 
11
- const { dateToString } = Deconz.ApiClient
12
11
  const { timeout } = homebridgeLib
13
12
  const { xyToHsv, hsvToXy, ctToXy } = homebridgeLib.Colour
14
13
 
15
- class Light extends homebridgeLib.ServiceDelegate {
16
- constructor (accessory, resource) {
17
- super(accessory, {
18
- id: resource.id,
19
- name: resource.body.name,
20
- subtype: resource.subtype,
21
- Service: accessory.Services.hap.Lightbulb
22
- })
23
- this.id = resource.id
24
- this.gateway = accessory.gateway
25
- this.client = accessory.client
26
- this.resource = resource
27
- this.rtype = resource.rtype
28
- this.rid = resource.rid
29
- this.rpath = resource.rpath +
30
- (resource.rtype === 'groups' ? '/action' : '/state')
31
- this.capabilities = resource.capabilities
32
-
33
- this.debug('%s: capabilities: %j', this.resource.rpath, this.capabilities)
34
-
35
- this.targetState = {}
36
- this.deferrals = []
14
+ class Light extends DeconzService.LightsResource {
15
+ constructor (accessory, resource, params = {}) {
16
+ params.Service = accessory.Services.hap.Lightbulb
17
+ super(accessory, resource, params)
37
18
 
38
19
  this.addCharacteristicDelegate({
39
20
  key: 'on',
40
21
  Characteristic: this.Characteristics.hap.On,
41
22
  value: this.capabilities.on
42
23
  ? this.resource.body.state.on
43
- : this.resource.body.state.all_on,
44
- setter: async (value) => { return this.put({ on: value }) }
45
- }).on('didSet', (value, fromHomekit) => {
46
- this.checkAdaptiveLighting()
24
+ : this.resource.body.state.all_on
25
+ }).on('didSet', (value, fromHomeKit) => {
26
+ if (fromHomeKit) {
27
+ this.put({ on: value })
28
+ this.updateAdaptiveLighting()
29
+ }
47
30
  })
48
31
 
49
32
  if (!this.capabilities.on) {
50
33
  this.addCharacteristicDelegate({
51
34
  key: 'anyOn',
52
35
  Characteristic: this.Characteristics.my.AnyOn,
53
- value: this.resource.body.state.any_on,
54
- setter: async (value) => { return this.put({ on: value }) }
55
- }).on('didSet', (value, fromHomekit) => {
56
- this.checkAdaptiveLighting()
36
+ value: this.resource.body.state.any_on
37
+ }).on('didSet', (value, fromHomeKit) => {
38
+ if (fromHomeKit) {
39
+ this.put({ on: value })
40
+ this.updateAdaptiveLighting()
41
+ }
57
42
  })
58
43
  }
59
44
 
@@ -62,23 +47,21 @@ class Light extends homebridgeLib.ServiceDelegate {
62
47
  key: 'brightness',
63
48
  Characteristic: this.Characteristics.hap.Brightness,
64
49
  unit: '%',
65
- value: this.resource.body.state.bri,
66
- setter: async (value) => {
67
- const bri = Math.round(value * 254.0 / 100.0)
68
- return this.put({ bri: bri })
50
+ value: Math.round(this.resource.body.state.bri / 2.54)
51
+ }).on('didSet', (value, fromHomeKit) => {
52
+ if (fromHomeKit) {
53
+ const bri = Math.round(value * 2.54)
54
+ this.put({ bri: bri })
55
+ this.updateAdaptiveLighting()
69
56
  }
70
- }).on('didSet', (value, fromHomekit) => {
71
- this.checkAdaptiveLighting()
72
57
  })
73
58
 
74
59
  this.addCharacteristicDelegate({
75
60
  key: 'brightnessChange',
76
61
  Characteristic: this.Characteristics.my.BrightnessChange,
77
- value: 0,
78
- setter: async (value) => {
79
- return this.put({ bri_inc: Math.round(value * 254.0 / 100.0) })
80
- }
81
- }).on('didSet', async () => {
62
+ value: 0
63
+ }).on('didSet', async (value) => {
64
+ this.put({ bri_inc: Math.round(value * 254.0 / 100.0) })
82
65
  await timeout(this.platform.config.waitTimeReset)
83
66
  this.values.brightnessChange = 0
84
67
  })
@@ -88,7 +71,13 @@ class Light extends homebridgeLib.ServiceDelegate {
88
71
  if (this.capabilities.ct || this.capabilities.xy || this.capabilities.hs) {
89
72
  this.addCharacteristicDelegate({
90
73
  key: 'colormode',
91
- value: this.resource.body.state.colormode
74
+ value: this.resource.body.state.colormode,
75
+ silent: true
76
+ }).on('didSet', (value) => {
77
+ this.resource.body.colormode = value
78
+ if (value !== 'ct') {
79
+ this.checkAdaptiveLighting()
80
+ }
92
81
  })
93
82
  }
94
83
 
@@ -101,118 +90,70 @@ class Light extends homebridgeLib.ServiceDelegate {
101
90
  minValue: this.capabilities.ctMin,
102
91
  maxValue: this.capabilities.ctMax
103
92
  },
104
- value: this.resource.body.state.ct,
105
- setter: async (value) => {
106
- const ct = Math.max(
107
- this.capabilities.ctMin, Math.min(value, this.capabilities.ctMax)
108
- )
109
- return this.put({ ct: ct })
110
- }
93
+ value: this.resource.body.state.ct
111
94
  }).on('didSet', (value, fromHomeKit) => {
95
+ this.checkAdaptiveLighting()
96
+ const ct = Math.max(
97
+ this.capabilities.ctMin, Math.min(value, this.capabilities.ctMax)
98
+ )
112
99
  if (fromHomeKit) {
113
- this.values.activeTransitionCount = 0
100
+ this.put({ ct: ct })
101
+ this.values.colormode = 'ct'
114
102
  }
115
- if (
116
- this.capabilities.xy && !this.capabilities.computesXy &&
117
- this.values.colormode === 'ct'
118
- ) {
119
- const { h, s } = xyToHsv(ctToXy(value), this.capabilities.gamut)
103
+ if (this.capabilities.xy && this.values.colormode === 'ct') {
104
+ const { h, s } = xyToHsv(ctToXy(ct), this.capabilities.gamut)
120
105
  this.values.hue = h
121
106
  this.values.saturation = s
122
107
  }
123
108
  })
124
-
125
- if (this.capabilities.bri) {
126
- this.addCharacteristicDelegate({
127
- key: 'supportedTransitionConfiguration',
128
- Characteristic: this.Characteristics.hap
129
- .SupportedCharacteristicValueTransitionConfiguration,
130
- silent: true
131
- })
132
- this.addCharacteristicDelegate({
133
- key: 'transitionControl',
134
- Characteristic: this.Characteristics.hap
135
- .CharacteristicValueTransitionControl,
136
- silent: true,
137
- getter: async () => {
138
- await this.initAdaptiveLighting()
139
- return this.adaptiveLighting.generateControl()
140
- },
141
- setter: async (value) => {
142
- await this.initAdaptiveLighting()
143
- this.adaptiveLighting.parseControl(value)
144
- const response = this.adaptiveLighting.generateControlResponse()
145
- this.values.activeTransitionCount = 1
146
- return response
147
- }
148
- })
149
- this.addCharacteristicDelegate({
150
- key: 'activeTransitionCount',
151
- Characteristic: this.Characteristics.hap
152
- .CharacteristicValueActiveTransitionCount,
153
- value: 0
154
- }).on('didSet', (value) => {
155
- if (!value) {
156
- this.adaptiveLighting.deactivate()
157
- } else {
158
- this.checkAdaptiveLighting()
159
- }
160
- })
161
- }
162
109
  }
163
110
 
164
111
  if (this.capabilities.xy) {
165
112
  this.addCharacteristicDelegate({
166
113
  key: 'hue',
167
114
  Characteristic: this.Characteristics.hap.Hue,
168
- unit: '°',
169
- setter: async (value) => {
170
- const xy = hsvToXy(value, this.values.saturation, this.capabilities.gamut)
171
- return this.put({ xy: xy })
172
- }
173
- }).on('didSet', (value, fromHomekit) => {
174
- if (fromHomekit) {
175
- this.values.activeTransitionCount = 0
115
+ unit: '°'
116
+ }).on('didSet', (value, fromHomeKit) => {
117
+ if (fromHomeKit) {
118
+ const xy = hsvToXy(
119
+ value, this.values.saturation, this.capabilities.gamut
120
+ )
121
+ this.put({ xy: xy })
122
+ this.values.colormode = 'xy'
176
123
  }
177
124
  })
178
125
  this.addCharacteristicDelegate({
179
126
  key: 'saturation',
180
127
  Characteristic: this.Characteristics.hap.Saturation,
181
- unit: '%',
182
- setter: async (value) => {
128
+ unit: '%'
129
+ }).on('didSet', (value, fromHomeKit) => {
130
+ if (fromHomeKit) {
183
131
  const xy = hsvToXy(this.values.hue, value, this.capabilities.gamut)
184
- return this.put({ xy: xy })
185
- }
186
- }).on('didSet', (value, fromHomekit) => {
187
- if (fromHomekit) {
188
- this.values.activeTransitionCount = 0
132
+ this.put({ xy: xy })
133
+ this.values.colormode = 'xy'
189
134
  }
190
135
  })
191
136
  } else if (this.capabilities.hs) {
192
137
  this.addCharacteristicDelegate({
193
138
  key: 'hue',
194
139
  Characteristic: this.Characteristics.hap.Hue,
195
- unit: '°',
196
- setter: async (value) => {
140
+ unit: '°'
141
+ }).on('didSet', (value, fromHomeKit) => {
142
+ if (fromHomeKit) {
197
143
  const hue = Math.round(this.hk.hue * 65535.0 / 360.0)
198
- return this.put({ hue: hue })
199
- }
200
- }).on('didSet', (value, fromHomekit) => {
201
- if (fromHomekit) {
202
- this.values.activeTransitionCount = 0
144
+ this.put({ hue: hue })
145
+ this.values.colormode = 'hs'
203
146
  }
204
147
  })
205
148
  this.addCharacteristicDelegate({
206
149
  key: 'saturation',
207
150
  Characteristic: this.Characteristics.hap.Saturation,
208
- unit: '%',
209
- setter: async (value) => {
151
+ unit: '%'
152
+ }).on('didSet', (value, fromHomeKit) => {
153
+ if (fromHomeKit) {
210
154
  const sat = Math.round(this.hk.sat * 254.0 / 100.0)
211
- return this.put({ sat: sat })
212
- }
213
- }).on('didSet', (value, fromHomekit) => {
214
- if (fromHomekit) {
215
- this.values.activeTransitionCount = 0
155
+ this.put({ sat: sat })
156
+ this.values.colormode = 'hs'
216
157
  }
217
158
  })
218
159
  }
@@ -220,36 +161,180 @@ class Light extends homebridgeLib.ServiceDelegate {
220
161
  if (this.capabilities.colorLoop) {
221
162
  this.addCharacteristicDelegate({
222
163
  key: 'colorLoop',
223
- Characteristic: this.Characteristics.my.ColorLoop,
224
- setter: async (value) => {
164
+ Characteristic: this.Characteristics.my.ColorLoop
165
+ }).on('didSet', (value, fromHomeKit) => {
166
+ if (fromHomeKit) {
225
167
  const effect = value ? 'colorloop' : 'none'
226
168
  const state = { effect: effect }
227
169
  if (value) {
228
170
  state.colorloopspeed = this.values.colorLoopSpeed
229
171
  }
230
- return this.put(state)
172
+ this.put(state)
173
+ this.values.colormode = 'hs'
231
174
  }
232
175
  })
233
176
  this.addCharacteristicDelegate({
234
177
  key: 'colorLoopSpeed',
235
178
  Characteristic: this.Characteristics.my.ColorLoopSpeed,
236
179
  unit: 's',
237
- value: 25,
180
+ value: 25
181
+ }).on('didSet', (value, fromHomeKit) => {
182
+ if (fromHomeKit) {
183
+ const effect = 'colorloop'
184
+ this.put({ effect: effect, colorloopspeed: value })
185
+ this.values.colormode = 'hs'
186
+ }
187
+ })
188
+ }
189
+
190
+ this.addCharacteristicDelegates()
191
+
192
+ if (this.capabilities.bri && this.capabilities.ct && !this.capabilities.hs) {
193
+ this.addCharacteristicDelegate({
194
+ key: 'supportedTransitionConfiguration',
195
+ Characteristic: this.Characteristics.hap
196
+ .SupportedCharacteristicValueTransitionConfiguration,
197
+ silent: true,
198
+ getter: async () => {
199
+ this.initAdaptiveLighting()
200
+ return this.adaptiveLighting.generateConfiguration()
201
+ }
202
+ })
203
+ this.addCharacteristicDelegate({
204
+ key: 'transitionControl',
205
+ Characteristic: this.Characteristics.hap
206
+ .CharacteristicValueTransitionControl,
207
+ silent: true,
208
+ getter: async () => {
209
+ return this.adaptiveLighting.generateControl()
210
+ },
238
211
  setter: async (value) => {
239
- return this.put({ effect: 'colorloop', colorloopspeed: value })
212
+ const control = this.adaptiveLighting.parseControl(value)
213
+ this.context.transitionControl = value
214
+ const response = this.adaptiveLighting.generateControlResponse()
215
+ const parsedResponse = this.adaptiveLighting.parseControl(response)
216
+ this.vdebug(
217
+ 'Adaptive Lighting: control update: %j => %j',
218
+ control, parsedResponse
219
+ )
220
+ this.values.activeTransitionCount = parsedResponse === '' ? 0 : 1
221
+ return response
222
+ }
223
+ })
224
+ this.addCharacteristicDelegate({
225
+ key: 'activeTransitionCount',
226
+ Characteristic: this.Characteristics.hap
227
+ .CharacteristicValueActiveTransitionCount,
228
+ silent: true,
229
+ value: 0
230
+ }).on('didSet', (value) => {
231
+ this.log('Adaptive Lighting: %sabled', value > 0 ? 'en' : 'dis')
232
+ if (value) {
233
+ this.updateAdaptiveLighting()
240
234
  }
241
235
  })
242
236
  }
243
237
 
244
- this.addCharacteristicDelegate({
245
- key: 'lastSeen',
246
- Characteristic: this.Characteristics.my.LastSeen
247
- })
238
+ if (this.resource.rtype === 'groups') {
239
+ this.sceneServices = []
240
+ for (const scene of this.resource.body.scenes) {
241
+ const service = new homebridgeLib.ServiceDelegate(accessory, {
242
+ name: this.resource.body.name + ' ' + scene.name,
243
+ Service: this.Services.hap.Switch,
244
+ subtype: this.subtype + '-S' + scene.id
245
+ })
246
+ service.addCharacteristicDelegate({
247
+ key: 'on',
248
+ Characteristic: this.Characteristics.hap.On,
249
+ value: false
250
+ }).on('didSet', async (value, fromHomeKit) => {
251
+ this.checkAdaptiveLighting()
252
+ if (fromHomeKit && value) {
253
+ try {
254
+ const path = this.resource.rpath + '/scenes/' + scene.id + '/recall'
255
+ this.debug('PUT %s', path)
256
+ await this.client.put(path)
257
+ } catch (error) { this.error(error) }
258
+ await timeout(this.platform.config.waitTimeReset)
259
+ service.values.on = false
260
+ }
261
+ })
262
+ this.sceneServices.push(service)
263
+ }
264
+ }
248
265
 
249
- this.addCharacteristicDelegate({
250
- key: 'statusFault',
251
- Characteristic: this.Characteristics.hap.StatusFault
252
- })
266
+ if (this.capabilities.effects != null) {
267
+ this.effectServices = []
268
+ this.addCharacteristicDelegate({
269
+ key: 'effect',
270
+ value: -1,
271
+ silent: true
272
+ }).on('didSet', (value) => {
273
+ for (const i in this.capabilities.effects) {
274
+ this.effectServices[i].values.on = value === i
275
+ }
276
+ })
277
+ for (const i in this.capabilities.effects) {
278
+ const effect = this.capabilities.effects[i]
279
+ const service = new homebridgeLib.ServiceDelegate(accessory, {
280
+ name: this.resource.body.name + ' ' + effect,
281
+ Service: this.Services.hap.Switch,
282
+ subtype: this.subtype + '-E' + i
283
+ })
284
+ service.addCharacteristicDelegate({
285
+ key: 'on',
286
+ Characteristic: this.Characteristics.hap.On
287
+ }).on('didSet', (value, fromHomeKit) => {
288
+ if (fromHomeKit) {
289
+ this.checkAdaptiveLighting()
290
+ this.values.effect = value ? i : -1
291
+ const newEffect = value
292
+ ? effect.toLowerCase()
293
+ : 'none'
294
+ this.put({ effect: newEffect })
295
+ }
296
+ })
297
+ this.effectServices.push(service)
298
+ }
299
+ // if (this.capabilities.effectSpeed) {
300
+ // this.addCharacteristicDelegate({
301
+ // key: 'effectSpeed',
302
+ // Characteristic: this.Characteristics.my.EffectSpeed,
303
+ // value: 50
304
+ // }).on('didSet', (value) => {
305
+ // this.setEffectSpeed(value)
306
+ // })
307
+ // this.hk.effectColours = []
308
+ // for (let i = 0; i <= 5; i++) {
309
+ // const service = new this.Service.Lightbulb(
310
+ // this.name + ' Effect Color ' + (i + 1), this.subtype + '-C' + i
311
+ // )
312
+ // service.addCharacteristicDelegate({
313
+ // key: 'on',
314
+ // Characteristic: this.Characteristics.hap.On,
315
+ // value: true
316
+ // }).on('didSet', (value) => {
317
+ // this.setEffectOn(i, value)
318
+ // })
319
+ // service.addCharacteristicDelegate({
320
+ // key: 'hue',
321
+ // Characteristic: this.Characteristics.hap.Hue,
322
+ // value: i * 60
323
+ // }).on('didSet', (value) => {
324
+ // this.setEffectHue(i, value)
325
+ // })
326
+ // service.addCharacteristicDelegate({
327
+ // key: 'saturation',
328
+ // Characteristic: this.Characteristics.hap.Saturation,
329
+ // value: 100
330
+ // }).on('didSet', (value) => {
331
+ // this.setEffectSat(i, value)
332
+ // })
333
+ // this.effectServices.push(service)
334
+ // }
335
+ // }
336
+ // this.checkEffect(this.obj.state.effect)
337
+ }
253
338
 
254
339
  this.settings = {
255
340
  brightnessAdjustment: 1,
@@ -257,72 +342,42 @@ class Light extends homebridgeLib.ServiceDelegate {
257
342
  waitTimeUpdate: this.platform.config.waitTimeUpdate,
258
343
  wallSwitch: false
259
344
  }
260
-
261
- this.update(this.resource.body)
262
- }
263
-
264
- update (body) {
265
- for (const key in body) {
266
- const value = body[key]
267
- switch (key) {
268
- case 'action':
269
- // Copied to `state` by `Resource` during polling.
270
- break
271
- case 'state':
272
- this.updateState(value)
273
- break
274
- case 'lastannounced':
275
- // this.values.lastBoot = dateToString(value)
276
- break
277
- case 'lastseen':
278
- this.values.lastSeen = dateToString(value)
279
- break
280
- case 'colorcapabilities':
281
- case 'ctmax':
282
- case 'ctmin':
283
- case 'devicemembership':
284
- case 'etag':
285
- case 'id':
286
- case 'lights':
287
- case 'hascolor':
288
- case 'manufacturername':
289
- case 'modelid':
290
- case 'name':
291
- case 'powerup':
292
- case 'scenes':
293
- case 'swversion':
294
- case 'type':
295
- case 'uniqueid':
296
- break
297
- default:
298
- this.warn('%s: unknown %s attribute', key, this.rtype)
299
- break
300
- }
301
- }
302
345
  }
303
346
 
304
347
  updateState (state) {
348
+ let updateAdaptiveLighting = false
305
349
  for (const key in state) {
306
350
  const value = state[key]
351
+ this.resource.body.state[key] = value
307
352
  switch (key) {
308
353
  case 'all_on':
309
354
  this.values.on = value
355
+ updateAdaptiveLighting = true
310
356
  break
311
357
  case 'any_on':
312
358
  this.values.anyOn = value
313
- break
314
- case 'alert':
359
+ updateAdaptiveLighting = true
315
360
  break
316
361
  case 'bri':
317
- this.values.brightness = Math.round(value * 100.0 / 254.0)
362
+ if (!this.recentlyUpdated) {
363
+ this.values.brightness = Math.round(value / 2.54)
364
+ updateAdaptiveLighting = true
365
+ }
318
366
  break
319
367
  case 'colormode':
320
368
  this.values.colormode = value
321
369
  break
322
370
  case 'ct':
323
- this.values.colorTemperature = value
371
+ if (!this.recentlyUpdated && this.values.colormode === 'ct') {
372
+ this.values.colorTemperature = value
373
+ }
324
374
  break
325
375
  case 'effect':
376
+ this.values.colorLoop = value === 'colorloop'
377
+ if (this.capabilities.effects != null) {
378
+ const effectName = value[0].toUpperCase() + value.slice(1)
379
+ this.values.effect = '' + this.capabilities.effects.indexOf(effectName)
380
+ }
326
381
  break
327
382
  case 'hue':
328
383
  if (!this.capabilities.xy) {
@@ -332,137 +387,91 @@ class Light extends homebridgeLib.ServiceDelegate {
332
387
  case 'on':
333
388
  this.values.on = value
334
389
  break
335
- case 'reachable':
336
- this.values.statusFault = value
337
- ? this.Characteristics.hap.StatusFault.NO_FAULT
338
- : this.Characteristics.hap.StatusFault.GENERAL_FAULT
339
- break
340
390
  case 'sat':
341
391
  if (!this.capabilities.xy) {
342
392
  this.values.hue = value
343
393
  }
344
394
  break
345
- case 'scene':
346
- break
347
395
  case 'xy':
348
- if (this.values.colormode !== 'ct' || this.capabilities.computesXy) {
396
+ if (
397
+ !this.recentlyUpdated &&
398
+ (this.values.colormode !== 'ct' || this.capabilities.computesXy)
399
+ ) {
349
400
  const { h, s } = xyToHsv(value, this.capabilities.gamut)
350
401
  this.values.hue = h
351
402
  this.values.saturation = s
352
403
  }
353
404
  break
354
405
  default:
355
- this.warn('state.%s: unknown %s attribute', key, this.rtype)
356
406
  break
357
407
  }
358
408
  }
409
+ if (updateAdaptiveLighting) {
410
+ this.updateAdaptiveLighting()
411
+ }
412
+ super.updateState(state)
359
413
  }
360
414
 
361
- async identify () {
362
- try {
363
- if (this.capabilities.alert) {
364
- if (this.capabilities.breathe) {
365
- await this.put({ alert: 'breathe' })
366
- await timeout(1500)
367
- return this.put({ alert: 'stop' })
368
- }
369
- return this.put({ alert: 'select' })
370
- }
371
- } catch (error) { this.warn(error) }
372
- }
373
-
374
- async initAdaptiveLighting () {
415
+ initAdaptiveLighting () {
375
416
  if (this.adaptiveLighting == null) {
376
417
  this.adaptiveLighting = new homebridgeLib.AdaptiveLighting(
377
418
  this.brightnessDelegate, this.colorTemperatureDelegate
378
419
  )
379
- this.values.supportedTransitionConfiguration =
380
- this.adaptiveLighting.generateConfiguration()
381
- if (
382
- this.values.activeTransitionCount === 1 &&
383
- this.values.transitionControl != null
384
- ) {
385
- this.adaptiveLighting.parseControl(this.values.transitionControl)
420
+ if (this.values.activeTransitionCount > 0) {
421
+ const control = this.adaptiveLighting.parseControl(
422
+ this.context.transitionControl
423
+ )
424
+ this.vdebug('Adaptive Lighting: restore control: %j', control)
425
+ this.adaptiveLighting.parseControl(this.context.transitionControl)
386
426
  }
427
+ this.log(
428
+ 'Adaptive Lighting: %sabled',
429
+ this.values.activeTransitionCount > 0 ? 'en' : 'dis'
430
+ )
387
431
  }
388
432
  }
389
433
 
390
- async checkAdaptiveLighting () {
391
- if (this.adaptiveLighting == null || !this.values.on) {
434
+ async updateAdaptiveLighting () {
435
+ if (
436
+ this.adaptiveLighting == null || // not supported
437
+ this.values.activeTransitionCount === 0 || // disabled
438
+ !this.values.on // light is off
439
+ ) {
392
440
  return
393
441
  }
394
442
  const ct = this.adaptiveLighting.getCt(
395
443
  this.values.brightness * this.settings.brightnessAdjustment
396
444
  )
397
- if (ct == null || ct === this.values.ct) {
445
+ if (ct == null) {
446
+ this.warn('assertion failed')
398
447
  return
399
448
  }
400
- return this.put({ ct: ct })
401
- }
402
-
403
- // Collect changes into a combined request.
404
- async put (state) {
405
- return new Promise((resolve, reject) => {
406
- for (const key in state) {
407
- this.targetState[key] = state[key]
408
- }
409
- const d = { resolve: resolve, reject: reject }
410
- this.deferrals.push(d)
411
- if (this.updating) {
412
- return
413
- }
414
- this.updating = true
415
- if (this.settings.waitTimeUpdate > 0) {
416
- setTimeout(() => {
417
- this._put()
418
- }, this.settings.waitTimeUpdate)
419
- } else {
420
- this._put()
421
- }
422
- })
449
+ this.debug(
450
+ '/%s/%d: adaptive lighting: {"state":{"ct": %d}}',
451
+ this.rtype, this.rid, ct
452
+ )
453
+ if (this.values.colormode === 'ct' && ct === this.values.colorTemperature) {
454
+ return
455
+ }
456
+ this.put({ ct: ct })
457
+ this.fromAdaptiveLighting = true
458
+ this.values.colormode = 'ct'
459
+ if (ct !== this.values.colorTemperature) {
460
+ this.values.colorTemperature = ct
461
+ } else if (this.capabilities.xy) { // colormode changed
462
+ const { h, s } = xyToHsv(ctToXy(ct), this.capabilities.gamut)
463
+ this.values.hue = h
464
+ this.values.saturation = s
465
+ }
466
+ this.fromAdaptiveLighting = false
423
467
  }
424
468
 
425
- // Send the request (for the combined changes) to the gateway.
426
- async _put () {
427
- const targetState = this.targetState
428
- const deferrals = this.deferrals
429
- this.targetState = {}
430
- this.deferrals = []
431
- this.updating = false
432
- if (
433
- this.gateway.transitionTime !== this.gateway.defaultTransitionTime &&
434
- targetState.transitiontime === undefined
435
- ) {
436
- targetState.transitiontime = this.gateway.transitionTime * 10
437
- this.gateway.resetTransitionTime()
438
- }
439
- if (this.capabilities.noTransition) {
440
- if (
441
- (
442
- targetState.on != null || targetState.bri != null ||
443
- targetState.bri_inc != null
444
- ) && (
445
- targetState.xy != null || targetState.ct != null ||
446
- targetState.hue != null || targetState.sat != null ||
447
- targetState.effect != null
448
- )
449
- ) {
450
- targetState.transitiontime = 0
451
- }
469
+ checkAdaptiveLighting (key, value) {
470
+ if (this.adaptiveLighting == null || this.fromAdaptiveLighting) {
471
+ return
452
472
  }
453
- this.client.put(this.rpath, targetState).then((obj) => {
454
- this.recentlyUpdated = true
455
- for (const d of deferrals) {
456
- d.resolve(true)
457
- }
458
- setTimeout(() => {
459
- this.recentlyUpdated = false
460
- }, 500)
461
- }).catch((error) => {
462
- for (const d of deferrals) {
463
- d.reject(error)
464
- }
465
- })
473
+ this.adaptiveLighting.deactivate()
474
+ this.values.activeTransitionCount = 0
466
475
  }
467
476
  }
468
477