homebridge-deconz 0.0.13 → 0.0.16

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.
@@ -256,7 +256,7 @@ class Resource {
256
256
  get prio () {
257
257
  if (this.rtype === 'groups') return -1
258
258
  if (this.rtype === 'lights') return this.endpoint
259
- return sensorsPrios.indexOf(this.type)
259
+ return sensorsPrios.indexOf(this.serviceName)
260
260
  }
261
261
 
262
262
  /** The resource path of the resource, e.g. `/lights/1`.
@@ -270,7 +270,7 @@ class Resource {
270
270
  * corresponding HomeKit service, or `null` for unsupported and unknown
271
271
  * resources.
272
272
  *
273
- * This is derived from the resource type and`type` in the resource body.
273
+ * This is derived from the resource type and `type` in the resource body.
274
274
  * @type {string}
275
275
  */
276
276
  get serviceName () {
@@ -656,7 +656,7 @@ class Resource {
656
656
  buttons.push([5, 'Next', SINGLE | LONG])
657
657
  break
658
658
  case 'TRADFRI wireless dimmer':
659
- if (this.obj.mode === 1) {
659
+ if (this.body.mode === 1) {
660
660
  buttons.push([1, 'Turn Right', SINGLE | LONG])
661
661
  buttons.push([2, 'Turn Left', SINGLE | LONG])
662
662
  } else {
@@ -44,6 +44,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
44
44
 
45
45
  this.gateway = this
46
46
  this.id = params.config.bridgeid
47
+ this.recommendedSoftware = this.platform.packageJson.engines.deCONZ
47
48
 
48
49
  /** Persisted properties.
49
50
  * @type {Object}
@@ -109,6 +110,9 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
109
110
  '%s %s gateway v%s', this.values.manufacturer, this.values.model,
110
111
  this.values.software
111
112
  )
113
+ if (this.values.software !== this.recommendedSoftware) {
114
+ this.warn('recommended version: deCONZ v%s', this.recommendedSoftware)
115
+ }
112
116
 
113
117
  /** Map of Accessory delegates by id for the gateway.
114
118
  * @type {Object<string, DeconzAccessory.Device>}
@@ -157,7 +161,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
157
161
  this.heartbeatEnabled = true
158
162
  this
159
163
  .on('identify', this.identify)
160
- .once('heartbeat', this.init)
164
+ .once('heartbeat', (beat) => { this.initialBeat = beat })
161
165
  .on('heartbeat', this.heartbeat)
162
166
  .on('shutdown', this.shutdown)
163
167
  }
@@ -182,6 +186,15 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
182
186
  this.values.manufacturer, this.values.model, this.values.software,
183
187
  this.nAccessories, this.nDevices, this.nResourcesMonitored
184
188
  )
189
+ if (this.values.software !== this.recommendedSoftware) {
190
+ this.warn('recommended version: deCONZ v%s', this.recommendedSoftware)
191
+ }
192
+ if (this.context.migration != null) {
193
+ this.log(
194
+ 'migration: %s: %d resources',
195
+ this.context.migration, this.nResourcesMonitored
196
+ )
197
+ }
185
198
  if (this.logLevel > 2) {
186
199
  this.vdebug(
187
200
  '%d gateway resouces: %j', this.nResources,
@@ -210,28 +223,24 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
210
223
  }
211
224
  }
212
225
 
213
- /** Initialise the gateway delegate.
214
- */
215
- async init (beat) {
216
- try {
217
- this.debug('initialising...')
218
- this.initialBeat = beat
219
- await this.connect()
220
- this.initialised = true
221
- this.debug('initialised')
222
- this.emit('initialised')
223
- } catch (error) { this.error(error) }
224
- }
225
-
226
226
  /** Update properties from gateway announcement.
227
227
  * @param {string} host - The gateway hostname or IP address and port.
228
228
  * @param {Object} config - The response body of an unauthenticated
229
229
  * GET `/config` (from {@link DeconzDiscovery#config config()}.
230
230
  */
231
- found (host, config) {
232
- this.values.host = host
233
- this.context.config = config
234
- this.values.software = config.swversion
231
+ async found (host, config) {
232
+ try {
233
+ this.context.host = host
234
+ this.values.host = host
235
+ this.context.config = config
236
+ this.values.software = config.swversion
237
+ if (!this.initialised) {
238
+ this.debug('initialising...')
239
+ await this.connect()
240
+ }
241
+ } catch (error) {
242
+ this.error(error)
243
+ }
235
244
  }
236
245
 
237
246
  async shutdown () {
@@ -412,8 +421,8 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
412
421
  for (const id in this.exposeErrorById) {
413
422
  this.resetExposeError(id)
414
423
  }
415
- this.context.fullState = null
416
424
  this.pollNext = true
425
+ this.pollFullState = true
417
426
  } catch (error) {
418
427
  if (
419
428
  error instanceof Deconz.ApiError && error.type === 101 && retry < 8
@@ -627,6 +636,45 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
627
636
 
628
637
  // ===========================================================================
629
638
 
639
+ async onUiGet (a) {
640
+ this.debug('ui request: GET %s', a.join('/'))
641
+ if (a.length === 0) {
642
+ return {
643
+ status: 200,
644
+ body: {
645
+ expose: this.service.values.expose,
646
+ groups: this.service.values.groups,
647
+ heartrate: this.service.values.heartrate,
648
+ lights: this.service.values.lights,
649
+ logLevel: this.service.values.logLevel,
650
+ schedules: this.service.values.schedules,
651
+ sensors: this.service.values.sensors
652
+ // deviceByRidByRtype: this.deviceByRidByRtype
653
+ }
654
+ }
655
+ }
656
+ if (a[0] !== 'accessories') {
657
+ return { status: 403 } // Forbidden
658
+ }
659
+ if (a.length === 1) {
660
+ return { status: 200, body: this.deviceByRidByRtype }
661
+ }
662
+ if (this.deviceById[a[1]] == null) {
663
+ return { status: 404 } // Not Found
664
+ }
665
+ if (a.length === 2) {
666
+ return { status: 200, body: this.deviceById[a[1]] }
667
+ }
668
+ return { status: 403 } // Forbidden
669
+ }
670
+
671
+ async onUiPut (a, body) {
672
+ this.debug('ui request: PUT %s %j', a.join('/'), body)
673
+ return { status: 501 } // Not Implented
674
+ }
675
+
676
+ // ===========================================================================
677
+
630
678
  /** Poll the gateway.
631
679
  *
632
680
  * Periodically get the gateway full state and call
@@ -639,9 +687,11 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
639
687
  try {
640
688
  this.polling = true
641
689
  this.vdebug('%spolling...', this.pollNext ? 'priority ' : '')
642
- if (this.context.fullState == null) {
643
- this.context.fullState = await this.client.get('/')
644
- this.context.fullState.groups[0] = await this.client.get('/groups/0')
690
+ if (this.context.fullState == null || this.pollFullState) {
691
+ const fullState = await this.client.get('/')
692
+ fullState.groups[0] = await this.client.get('/groups/0')
693
+ this.context.fullState = fullState
694
+ this.pollFullState = false
645
695
  } else {
646
696
  const config = await this.client.get('/config')
647
697
  if (config.bridgeid === this.id && config.UTC == null) {
@@ -674,6 +724,11 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
674
724
  this.pollNext = false
675
725
  this.polling = false
676
726
  }
727
+ if (!this.initialised) {
728
+ this.initialised = true
729
+ this.debug('initialised')
730
+ this.emit('initialised')
731
+ }
677
732
  }
678
733
 
679
734
  /** Analyse the peristed full state of the gateway,
@@ -857,6 +912,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
857
912
  }
858
913
 
859
914
  this.nAccessories = Object.keys(this.accessoryById).length
915
+ this.nResourcesMonitored = Object.keys(this.accessoryByRpath).length
860
916
  this.nExposeErrors = Object.keys(this.exposeErrorById).length
861
917
  if (this.nExposeErrors === 0) {
862
918
  this.vdebug('%d accessories', this.nAccessories)
@@ -867,8 +923,6 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
867
923
  }
868
924
 
869
925
  if (changed) {
870
- this.nResourcesMonitored = Object.keys(this.accessoryByRpath).length
871
- this.identify()
872
926
  if (this.context.migration == null) {
873
927
  const response = await this.client.post('/resourcelinks', {
874
928
  name: 'homebridge-deconz',
@@ -882,10 +936,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
882
936
  links: Object.keys(this.accessoryByRpath).sort()
883
937
  })
884
938
  }
885
- this.log(
886
- 'migration: %s: %d resources',
887
- this.context.migration, this.nResourcesMonitored
888
- )
939
+ this.identify()
889
940
  }
890
941
  }
891
942
 
@@ -39,7 +39,7 @@ class DeconzAccessory extends homebridgeLib.AccessoryDelegate {
39
39
  manufacturer: device.resource.manufacturer,
40
40
  model: device.resource.model,
41
41
  firmware: device.resource.firmware,
42
- category: category
42
+ category
43
43
  })
44
44
 
45
45
  this.context.gid = gateway.id
@@ -45,6 +45,7 @@ class DeconzPlatform extends homebridgeLib.Platform {
45
45
  .stringKey('name')
46
46
  .stringKey('platform')
47
47
  .boolKey('forceHttp')
48
+ .stringKey('host')
48
49
  .arrayKey('hosts')
49
50
  .boolKey('noResponse')
50
51
  .intKey('parallelRequests', 1, 30)
@@ -60,19 +61,26 @@ class DeconzPlatform extends homebridgeLib.Platform {
60
61
 
61
62
  try {
62
63
  optionParser.parse(configJson)
64
+ if (this.config.host != null) {
65
+ this.config.hosts.push(this.config.host)
66
+ }
63
67
  this.discovery = new Deconz.Discovery({
64
68
  forceHttp: this.config.forceHttp,
65
69
  timeout: this.config.timeout
66
70
  })
67
71
  this.discovery
68
72
  .on('error', (error) => {
69
- this.log(
70
- '%s: request %d: %s %s', error.request.name,
71
- error.request.id, error.request.method, error.request.resource
72
- )
73
- this.warn(
74
- '%s: request %d: %s', error.request.name, error.request.id, error
75
- )
73
+ if (error instanceof homebridgeLib.HttpClient.HttpError) {
74
+ this.log(
75
+ '%s: request %d: %s %s', error.request.name,
76
+ error.request.id, error.request.method, error.request.resource
77
+ )
78
+ this.warn(
79
+ '%s: request %d: %s', error.request.name, error.request.id, error
80
+ )
81
+ return
82
+ }
83
+ this.warn(error)
76
84
  })
77
85
  .on('request', (request) => {
78
86
  this.debug(
@@ -101,12 +109,10 @@ class DeconzPlatform extends homebridgeLib.Platform {
101
109
  async foundGateway (host, config) {
102
110
  const id = config.bridgeid
103
111
  if (this.gatewayMap[id] == null) {
104
- this.gatewayMap[id] = new DeconzAccessory.Gateway(this, {
105
- config: config,
106
- host: host
107
- })
112
+ this.gatewayMap[id] = new DeconzAccessory.Gateway(this, { config, host })
108
113
  }
109
- this.gatewayMap[id].found(host, config)
114
+ await this.gatewayMap[id].found(host, config)
115
+ await events.once(this.gatewayMap[id], 'initialised')
110
116
  this.emit('found')
111
117
  }
112
118
 
@@ -124,17 +130,25 @@ class DeconzPlatform extends homebridgeLib.Platform {
124
130
  async init () {
125
131
  try {
126
132
  const jobs = []
127
- this.debug('job %d: find at least one gateway', jobs.length)
128
- jobs.push(events.once(this, 'found'))
129
133
  if (this.config.hosts.length > 0) {
130
134
  for (const host of this.config.hosts) {
131
135
  this.debug('job %d: find gateway at %s', jobs.length, host)
132
136
  jobs.push(this.findHost(host))
133
137
  }
134
138
  } else {
139
+ this.debug('job %d: find at least one gateway', jobs.length)
140
+ jobs.push(events.once(this, 'found'))
135
141
  for (const id in this.gatewayMap) {
142
+ const gateway = this.gatewayMap[id]
143
+ const host = gateway.context.host
136
144
  this.debug('job %d: find gateway %s', jobs.length, id)
137
- jobs.push(this.gatewayMap[id].init())
145
+ jobs.push(events.once(gateway, 'initialised'))
146
+ try {
147
+ const config = await this.discovery.config(host)
148
+ await this.foundGateway(host, config)
149
+ } catch (error) {
150
+ this.warn('%s: %s', id, error)
151
+ }
138
152
  }
139
153
  }
140
154
 
@@ -150,9 +164,61 @@ class DeconzPlatform extends homebridgeLib.Platform {
150
164
 
151
165
  this.log('%d gateways', Object.keys(this.gatewayMap).length)
152
166
  this.emit('initialised')
167
+ const dumpInfo = {
168
+ config: this.config,
169
+ gatewayMap: {}
170
+ }
171
+ for (const id in this.gatewayMap) {
172
+ const gateway = this.gatewayMap[id]
173
+ dumpInfo.gatewayMap[id] = gateway.context
174
+ }
175
+ await this.createDumpFile(dumpInfo)
153
176
  } catch (error) { this.error(error) }
154
177
  }
155
178
 
179
+ async onUiRequest (method, url, body) {
180
+ const a = url.split('/').slice(1)
181
+ if (a.length < 1) {
182
+ return { status: 403 } // Forbidden
183
+ }
184
+ if (a[0] === 'gateways') {
185
+ if (a.length === 1) {
186
+ if (method === 'GET') {
187
+ // const gatewayByHost = await this.discovery.discover()
188
+ const gatewayByHost = {}
189
+ for (const id in this.gatewayMap) {
190
+ const gateway = this.gatewayMap[id]
191
+ gatewayByHost[gateway.context.host] = {
192
+ config: gateway.context.config,
193
+ host: gateway.context.host,
194
+ id
195
+ }
196
+ }
197
+ return {
198
+ status: 200,
199
+ body: Object.keys(gatewayByHost).sort().map((host) => {
200
+ return gatewayByHost[host]
201
+ })
202
+ }
203
+ }
204
+ return { status: 405 } // Method Not Allowed
205
+ }
206
+ const gateway = this.gatewayMap[a[1]]
207
+ const path = a.slice(2)
208
+ if (gateway == null) {
209
+ return { status: 404 } // Not Found
210
+ }
211
+ if (method === 'GET') {
212
+ return gateway.onUiGet(path)
213
+ }
214
+ if (method === 'PUT') {
215
+ return gateway.onUiPut(path, body)
216
+ }
217
+ return { status: 405 } // Method Not Allowed
218
+ }
219
+ return { status: 403 } // Forbidden
220
+ }
221
+
156
222
  async heartbeat (beat) {
157
223
  try {
158
224
  if (beat % 300 === 5 && this.config.hosts.length === 0) {
@@ -57,7 +57,7 @@ class Consumption extends DeconzService.SensorsResource {
57
57
  }
58
58
 
59
59
  updateState (state) {
60
- Consumption.updateResourceState(this.service, state)
60
+ Consumption.updateResourceState(this, state)
61
61
  super.updateState(state)
62
62
  }
63
63
  }
@@ -51,7 +51,7 @@ class Light extends DeconzService.LightsResource {
51
51
  }).on('didSet', (value, fromHomeKit) => {
52
52
  if (fromHomeKit) {
53
53
  const bri = Math.round(value * 2.54)
54
- this.put({ bri: bri })
54
+ this.put({ bri })
55
55
  this.updateAdaptiveLighting()
56
56
  }
57
57
  })
@@ -97,7 +97,7 @@ class Light extends DeconzService.LightsResource {
97
97
  this.capabilities.ctMin, Math.min(value, this.capabilities.ctMax)
98
98
  )
99
99
  if (fromHomeKit) {
100
- this.put({ ct: ct })
100
+ this.put({ ct })
101
101
  this.values.colormode = 'ct'
102
102
  }
103
103
  if (this.capabilities.xy && this.values.colormode === 'ct') {
@@ -118,7 +118,7 @@ class Light extends DeconzService.LightsResource {
118
118
  const xy = hsvToXy(
119
119
  value, this.values.saturation, this.capabilities.gamut
120
120
  )
121
- this.put({ xy: xy })
121
+ this.put({ xy })
122
122
  this.values.colormode = 'xy'
123
123
  }
124
124
  })
@@ -129,7 +129,7 @@ class Light extends DeconzService.LightsResource {
129
129
  }).on('didSet', (value, fromHomeKit) => {
130
130
  if (fromHomeKit) {
131
131
  const xy = hsvToXy(this.values.hue, value, this.capabilities.gamut)
132
- this.put({ xy: xy })
132
+ this.put({ xy })
133
133
  this.values.colormode = 'xy'
134
134
  }
135
135
  })
@@ -140,8 +140,8 @@ class Light extends DeconzService.LightsResource {
140
140
  unit: '°'
141
141
  }).on('didSet', (value, fromHomeKit) => {
142
142
  if (fromHomeKit) {
143
- const hue = Math.round(this.hk.hue * 65535.0 / 360.0)
144
- this.put({ hue: hue })
143
+ const hue = Math.round(this.values.hue * 65535.0 / 360.0)
144
+ this.put({ hue })
145
145
  this.values.colormode = 'hs'
146
146
  }
147
147
  })
@@ -151,8 +151,8 @@ class Light extends DeconzService.LightsResource {
151
151
  unit: '%'
152
152
  }).on('didSet', (value, fromHomeKit) => {
153
153
  if (fromHomeKit) {
154
- const sat = Math.round(this.hk.sat * 254.0 / 100.0)
155
- this.put({ sat: sat })
154
+ const sat = Math.round(this.values.saturation * 254.0 / 100.0)
155
+ this.put({ sat })
156
156
  this.values.colormode = 'hs'
157
157
  }
158
158
  })
@@ -165,7 +165,7 @@ class Light extends DeconzService.LightsResource {
165
165
  }).on('didSet', (value, fromHomeKit) => {
166
166
  if (fromHomeKit) {
167
167
  const effect = value ? 'colorloop' : 'none'
168
- const state = { effect: effect }
168
+ const state = { effect }
169
169
  if (value) {
170
170
  state.colorloopspeed = this.values.colorLoopSpeed
171
171
  }
@@ -181,7 +181,7 @@ class Light extends DeconzService.LightsResource {
181
181
  }).on('didSet', (value, fromHomeKit) => {
182
182
  if (fromHomeKit) {
183
183
  const effect = 'colorloop'
184
- this.put({ effect: effect, colorloopspeed: value })
184
+ this.put({ effect, colorloopspeed: value })
185
185
  this.values.colormode = 'hs'
186
186
  }
187
187
  })
@@ -236,31 +236,8 @@ class Light extends DeconzService.LightsResource {
236
236
  }
237
237
 
238
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
- }
239
+ this.sceneServices = {}
240
+ this.updateScenes(this.resource.body.scenes)
264
241
  }
265
242
 
266
243
  if (this.capabilities.effects != null) {
@@ -412,6 +389,43 @@ class Light extends DeconzService.LightsResource {
412
389
  super.updateState(state)
413
390
  }
414
391
 
392
+ updateScenes (scenes) {
393
+ const sceneById = {}
394
+ for (const scene of scenes) {
395
+ sceneById[scene.id] = scene
396
+ if (this.sceneServices[scene.id] == null) {
397
+ const service = new homebridgeLib.ServiceDelegate(this.accessoryDelegate, {
398
+ name: this.resource.body.name + ' ' + scene.name,
399
+ Service: this.Services.hap.Switch,
400
+ subtype: this.subtype + '-S' + scene.id
401
+ })
402
+ service.addCharacteristicDelegate({
403
+ key: 'on',
404
+ Characteristic: this.Characteristics.hap.On,
405
+ value: false
406
+ }).on('didSet', async (value, fromHomeKit) => {
407
+ this.checkAdaptiveLighting()
408
+ if (fromHomeKit && value) {
409
+ try {
410
+ const path = this.resource.rpath + '/scenes/' + scene.id + '/recall'
411
+ this.debug('PUT %s', path)
412
+ await this.client.put(path)
413
+ } catch (error) { this.error(error) }
414
+ await timeout(this.platform.config.waitTimeReset)
415
+ service.values.on = false
416
+ }
417
+ })
418
+ this.sceneServices[scene.id] = service
419
+ }
420
+ }
421
+ for (const id in this.scenesServices) {
422
+ if (sceneById[id] == null) {
423
+ this.scenesSerices[id].destroy()
424
+ delete this.scenesService[id]
425
+ }
426
+ }
427
+ }
428
+
415
429
  initAdaptiveLighting () {
416
430
  if (this.adaptiveLighting == null) {
417
431
  this.adaptiveLighting = new homebridgeLib.AdaptiveLighting(
@@ -453,7 +467,7 @@ class Light extends DeconzService.LightsResource {
453
467
  if (this.values.colormode === 'ct' && ct === this.values.colorTemperature) {
454
468
  return
455
469
  }
456
- this.put({ ct: ct })
470
+ this.put({ ct })
457
471
  this.fromAdaptiveLighting = true
458
472
  this.values.colormode = 'ct'
459
473
  if (ct !== this.values.colorTemperature) {
@@ -35,7 +35,7 @@ class Motion extends DeconzService.SensorsResource {
35
35
  : value === this.Characteristics.eve.Sensitivity.LOW
36
36
  ? 0
37
37
  : Math.round(this.sensitivitymax / 2)
38
- await this.put('/config', { sensitivity: sensitivity })
38
+ await this.put('/config', { sensitivity })
39
39
  }
40
40
  })
41
41
 
@@ -75,7 +75,7 @@ class Power extends DeconzService.SensorsResource {
75
75
  }
76
76
 
77
77
  updateState (state) {
78
- Power.updateResourceState(this.service, state)
78
+ Power.updateResourceState(this, state)
79
79
  super.updateState(state)
80
80
  }
81
81
  }
@@ -42,8 +42,8 @@ class Switch extends DeconzService.SensorsResource {
42
42
  this.buttonServices[i] = new DeconzService.Button(this.accessoryDelegate, {
43
43
  name: this.name + ' ' + label,
44
44
  button: Number(i),
45
- events: events,
46
- hasRepeat: hasRepeat
45
+ events,
46
+ hasRepeat
47
47
  })
48
48
  }
49
49
  }
@@ -27,6 +27,7 @@ class Temperature extends DeconzService.SensorsResource {
27
27
  key: 'offset',
28
28
  Characteristic: this.Characteristics.my.Offset,
29
29
  unit: '°C',
30
+ props: { minValue: -5, maxValue: 5, minStep: 0.1 },
30
31
  value: 0
31
32
  }).on('didSet', async (value, fromHomeKit) => {
32
33
  if (fromHomeKit) {
@@ -65,7 +65,7 @@ class Thermostat extends DeconzService.SensorsResource {
65
65
  }
66
66
  }).on('didSet', async (value, fromHomeKit) => {
67
67
  if (fromHomeKit) {
68
- await this.put('/mode', {
68
+ await this.put('/config', {
69
69
  mode: value === this.Characteristics.hap.TargetHeatingCoolingState.OFF
70
70
  ? 'off'
71
71
  : this.capabilities.heatValue
@@ -77,6 +77,7 @@ class Thermostat extends DeconzService.SensorsResource {
77
77
  key: 'offset',
78
78
  Characteristic: this.Characteristics.my.Offset,
79
79
  unit: '°C',
80
+ props: { minValue: -5, maxValue: 5, minStep: 0.1 },
80
81
  value: 0
81
82
  }).on('didSet', async (value, fromHomeKit) => {
82
83
  if (fromHomeKit) {
@@ -37,10 +37,6 @@ class WindowCovering extends DeconzService.LightsResource {
37
37
  key: 'positionState',
38
38
  Characteristic: this.Characteristics.hap.PositionState,
39
39
  value: this.Characteristics.hap.PositionState.STOPPED
40
- }).on('didSet', (value) => {
41
- if (value === this.Characteristics.hap.PositionState.STOPPED) {
42
- this.values.targetPosition = this.values.currentPosition
43
- }
44
40
  })
45
41
 
46
42
  this.addCharacteristicDelegate({
@@ -103,10 +99,6 @@ class WindowCovering extends DeconzService.LightsResource {
103
99
  }
104
100
 
105
101
  async setPosition () {
106
- if (this.timer != null) {
107
- clearTimeout(this.timer)
108
- delete this.timer
109
- }
110
102
  let lift = 100 - this.values.targetPosition // % closed --> % open
111
103
  if (this.venetianBlind) {
112
104
  if (this.values.closeUpwards) {
@@ -121,11 +113,8 @@ class WindowCovering extends DeconzService.LightsResource {
121
113
  this.values.targetPosition > this.values.currentPosition
122
114
  ? this.Characteristics.hap.PositionState.INCREASING
123
115
  : this.Characteristics.hap.PositionState.DECREASING
124
- await this.put({ lift: lift })
125
- this.timer = setTimeout(() => {
126
- this.values.positionState =
127
- this.Characteristics.hap.PositionState.STOPPED
128
- }, 15000)
116
+ this.moving = new Date()
117
+ await this.put({ lift })
129
118
  }
130
119
 
131
120
  updateState (state) {
@@ -146,9 +135,13 @@ class WindowCovering extends DeconzService.LightsResource {
146
135
  this.values.closeUpwards = closeUpwards
147
136
  }
148
137
  if (
149
- position === this.values.targetPosition &&
150
- (closeUpwards == null || closeUpwards === this.targetCloseUpwards)
138
+ this.moving == null || new Date() - this.moving >= 30000 || (
139
+ position === this.values.targetPosition &&
140
+ (closeUpwards == null || closeUpwards === this.targetCloseUpwards)
141
+ )
151
142
  ) {
143
+ this.moving = null
144
+ this.values.targetPosition = position
152
145
  this.values.positionState = this.Characteristics.hap.PositionState.STOPPED
153
146
  }
154
147
  }
@@ -91,6 +91,9 @@ class DeconzService extends homebridgeLib.ServiceDelegate {
91
91
  if (body.state != null) {
92
92
  this.updateState(body.state, rpath)
93
93
  }
94
+ if (body.scenes != null && this.rtype === 'groups') {
95
+ this.updateScenes(body.scenes)
96
+ }
94
97
  }
95
98
  }
96
99