homebridge-deconz 0.0.27 → 0.1.1

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 (39) hide show
  1. package/config.schema.json +0 -7
  2. package/lib/Deconz/Resource.js +27 -4
  3. package/lib/DeconzAccessory/AirPurifier.js +0 -2
  4. package/lib/DeconzAccessory/Gateway.js +298 -149
  5. package/lib/DeconzAccessory/Light.js +27 -37
  6. package/lib/DeconzAccessory/Sensor.js +91 -1
  7. package/lib/DeconzAccessory/Thermostat.js +1 -3
  8. package/lib/DeconzAccessory/WarningDevice.js +0 -8
  9. package/lib/DeconzAccessory/WindowCovering.js +0 -2
  10. package/lib/DeconzAccessory/index.js +123 -13
  11. package/lib/DeconzPlatform.js +12 -20
  12. package/lib/DeconzService/AirPressure.js +1 -1
  13. package/lib/DeconzService/AirPurifier.js +2 -2
  14. package/lib/DeconzService/AirQuality.js +1 -1
  15. package/lib/DeconzService/Alarm.js +1 -1
  16. package/lib/DeconzService/Battery.js +19 -9
  17. package/lib/DeconzService/Button.js +1 -0
  18. package/lib/DeconzService/CarbonMonoxide.js +1 -1
  19. package/lib/DeconzService/Consumption.js +3 -1
  20. package/lib/DeconzService/Contact.js +1 -25
  21. package/lib/DeconzService/Daylight.js +22 -19
  22. package/lib/DeconzService/Flag.js +1 -1
  23. package/lib/DeconzService/Gateway.js +48 -0
  24. package/lib/DeconzService/Humidity.js +1 -1
  25. package/lib/DeconzService/Leak.js +1 -1
  26. package/lib/DeconzService/Light.js +111 -86
  27. package/lib/DeconzService/LightLevel.js +3 -3
  28. package/lib/DeconzService/Motion.js +3 -9
  29. package/lib/DeconzService/Power.js +2 -1
  30. package/lib/DeconzService/Status.js +1 -1
  31. package/lib/DeconzService/Switch.js +1 -1
  32. package/lib/DeconzService/WindowCovering.js +14 -4
  33. package/lib/DeconzService/index.js +11 -12
  34. package/package.json +6 -6
  35. package/lib/DeconzAccessory/Contact.js +0 -53
  36. package/lib/DeconzAccessory/Motion.js +0 -53
  37. package/lib/DeconzAccessory/Temperature.js +0 -61
  38. package/lib/DeconzService/DeviceSettings.js +0 -65
  39. package/lib/DeconzService/GatewaySettings.js +0 -176
@@ -50,24 +50,15 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
50
50
  * @type {Object}
51
51
  * @property {Object} config - Response body of unauthenticated
52
52
  * GET `/config` (from {@link DeconzDiscovery#config config()}.
53
- * @property {Object.<string, boolean>} blacklist - Map of blacklisted
54
- * devices.
55
53
  * @property {Object} fullState - The gateway's full state, from the
56
54
  * last time the gateway was polled.
57
- * @property {Object} settings - The persisted settings, maintained through
55
+ * @property {Object.<String, Object>} settingsById - The persisted settings, maintained through
58
56
  * the Homebridge UI.
59
57
  */
60
58
  this.context // eslint-disable-line no-unused-expressions
61
- this.context.host = params.host
62
59
  this.context.config = params.config
63
60
  if (this.context.settingsById == null) {
64
61
  this.context.settingsById = {}
65
- // migration
66
- for (const id in this.context.blacklist) {
67
- this.context.settingsById[id] = { expose: false }
68
- }
69
- delete this.context.blacklist
70
- // end migration
71
62
  }
72
63
  if (this.context.fullState != null) {
73
64
  this.analyseFullState(this.context.fullState, {
@@ -83,6 +74,41 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
83
74
  this.client.apiKey = value
84
75
  })
85
76
 
77
+ this.addPropertyDelegate({
78
+ key: 'autoExpose',
79
+ value: { groups: false, lights: true, schedules: false, sensors: true },
80
+ silent: true
81
+ }).on('didSet', async () => {
82
+ this.pollNext = true
83
+ })
84
+
85
+ this.addPropertyDelegate({
86
+ key: 'brightnessAdjustment',
87
+ value: 1,
88
+ silent: true
89
+ })
90
+
91
+ this.addPropertyDelegate({
92
+ key: 'expose',
93
+ value: true,
94
+ silent: true
95
+ }).on('didSet', async (value) => {
96
+ try {
97
+ this.service.values.statusActive = value
98
+ if (value) {
99
+ await this.connect()
100
+ } else {
101
+ await this.reset()
102
+ }
103
+ } catch (error) { this.error(error) }
104
+ })
105
+
106
+ this.addPropertyDelegate({
107
+ key: 'heartrate',
108
+ value: 30,
109
+ silent: true
110
+ })
111
+
86
112
  this.addPropertyDelegate({
87
113
  key: 'host',
88
114
  value: params.host,
@@ -99,11 +125,54 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
99
125
  this.values.host = params.host
100
126
 
101
127
  this.addPropertyDelegate({
102
- key: 'rtypes',
103
- value: [],
128
+ key: 'periodicEvents',
129
+ value: true,
104
130
  silent: true
105
- }).on('didSet', async () => {
106
- this.pollNext = true
131
+ })
132
+
133
+ this.addPropertyDelegate({
134
+ key: 'restart',
135
+ value: false,
136
+ silent: true
137
+ }).on('didSet', async (value) => {
138
+ if (value) {
139
+ try {
140
+ await this.client.restart()
141
+ this.values.search = false
142
+ this.values.unlock = false
143
+ return
144
+ } catch (error) { this.warn(error) }
145
+ }
146
+ })
147
+
148
+ this.addPropertyDelegate({
149
+ key: 'search',
150
+ value: false,
151
+ silent: true
152
+ }).on('didSet', async (value) => {
153
+ if (value) {
154
+ try {
155
+ await this.client.search()
156
+ await homebridgeLib.timeout(120000)
157
+ this.values.search = false
158
+ return
159
+ } catch (error) { this.warn(error) }
160
+ }
161
+ })
162
+
163
+ this.addPropertyDelegate({
164
+ key: 'unlock',
165
+ value: false,
166
+ silent: true
167
+ }).on('didSet', async (value) => {
168
+ if (value) {
169
+ try {
170
+ await this.client.unlock()
171
+ await homebridgeLib.timeout(60000)
172
+ this.values.unlock = false
173
+ return
174
+ } catch (error) { this.warn(error) }
175
+ }
107
176
  })
108
177
 
109
178
  this.addPropertyDelegate({
@@ -142,20 +211,14 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
142
211
  */
143
212
  this.exposeErrorById = {}
144
213
 
145
- /** Map of services to un-blacklist a gateway device.
146
- * @type {Object<string, DeconzService.DeviceSettings>}
147
- */
148
- this.serviceById = {}
149
-
150
214
  /** The service delegate for the Gateway Settings settings.
151
215
  * @type {DeconzService.GatewaySettings}
152
216
  */
153
- this.service = new DeconzService.GatewaySettings(this, {
154
- name: this.name + ' Gateway Settings',
217
+ this.service = new DeconzService.Gateway(this, {
218
+ name: this.name + ' Gateway',
155
219
  primaryService: true,
156
220
  host: params.host
157
221
  })
158
- this.manageLogLevel(this.service.characteristicDelegate('logLevel'))
159
222
 
160
223
  /** The service delegate for the Stateless Programmable Switch service.
161
224
  * @type {DeconzService.Button}
@@ -164,8 +227,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
164
227
  name: this.name + ' Button',
165
228
  button: 1,
166
229
  events: DeconzService.Button.SINGLE | DeconzService.Button.DOUBLE |
167
- DeconzService.Button.LONG,
168
- exposeConfiguredName: true
230
+ DeconzService.Button.LONG
169
231
  })
170
232
 
171
233
  this.createClient()
@@ -242,7 +304,6 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
242
304
  */
243
305
  async found (host, config) {
244
306
  try {
245
- this.context.host = host
246
307
  this.values.host = host
247
308
  this.context.config = config
248
309
  this.values.software = config.swversion
@@ -256,6 +317,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
256
317
  }
257
318
 
258
319
  async shutdown () {
320
+ this.service.values.statusActive = false
259
321
  return this.wsClient.close()
260
322
  }
261
323
 
@@ -265,14 +327,14 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
265
327
  async heartbeat (beat) {
266
328
  beat -= this.initialBeat
267
329
  try {
268
- if (beat > 0) {
330
+ if (this.values.periodicEvents && beat > 0) {
269
331
  for (const { rate, event } of periodicEvents) {
270
332
  if (beat % rate === 0) {
271
333
  this.buttonService.update(event)
272
334
  }
273
335
  }
274
336
  }
275
- if (beat - this.pollBeat >= this.service.values.heartrate || this.pollNext) {
337
+ if (beat - this.pollBeat >= this.values.heartrate || this.pollNext) {
276
338
  this.pollBeat = beat
277
339
  await this.poll()
278
340
  }
@@ -391,13 +453,13 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
391
453
  })
392
454
  .on('added', (rtype, rid, body) => {
393
455
  this.vdebug('/%s/%d: added: %j', rtype, rid, body)
394
- if (this.service.values[rtype]) {
456
+ if (this.values.autoExpose[rtype]) {
395
457
  this.pollNext = true
396
458
  }
397
459
  })
398
460
  .on('deleted', (rtype, rid) => {
399
461
  this.vdebug('/%s/%d: deleted', rtype, rid)
400
- if (this.service.values[rtype]) {
462
+ if (this.values.autoExpose[rtype]) {
401
463
  this.pollNext = true
402
464
  }
403
465
  })
@@ -419,7 +481,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
419
481
  * gateway, and analyse the full state.
420
482
  */
421
483
  async connect (retry = 0) {
422
- if (!this.service.values.expose) {
484
+ if (!this.values.expose) {
423
485
  this.warn('unlock gateway and set Expose to obtain an API key')
424
486
  return
425
487
  }
@@ -429,6 +491,8 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
429
491
  await this.client.getApiKey('homebridge-deconz')
430
492
  }
431
493
  this.wsClient.listen()
494
+ this.service.values.restart = false
495
+ this.service.values.statusActive = true
432
496
  this.checkApiKeys = true
433
497
  for (const id in this.exposeErrorById) {
434
498
  this.resetExposeError(id)
@@ -444,7 +508,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
444
508
  return this.connect(retry + 1)
445
509
  }
446
510
  this.error(error)
447
- this.service.values.expose = false
511
+ this.values.expose = false
448
512
  }
449
513
  }
450
514
 
@@ -473,26 +537,14 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
473
537
  this.deleteAccessory(id)
474
538
  }
475
539
  }
476
- for (const id in this.serviceById) {
477
- this.deleteService(id)
478
- }
479
540
  this.exposeErrors = {}
480
541
  this.context.settingsById = {}
481
- this.context.settingsById[this.id] = {
482
- autoExposeGroups: false,
483
- autoExposeLights: false,
484
- autoExposeSensors: false,
485
- autoExposeSchedules: false,
486
- logLevel: 2
487
- }
488
542
  this.context.fullState = null
489
543
  this.context.migration = null
490
- this.service.values.lights = false
491
- this.service.values.sensors = false
492
- this.service.values.groups = false
493
- this.service.values.schedules = false
494
- this.service.values.logLevel = 2
495
- this.values.rtypes = []
544
+ this.values.logLevel = 2
545
+ this.values.autoExpose = {
546
+ groups: false, lights: true, schedules: false, sensors: true
547
+ }
496
548
  } catch (error) { this.error(error) }
497
549
  }
498
550
 
@@ -517,6 +569,25 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
517
569
  this.pollNext = true
518
570
  }
519
571
 
572
+ /** Re-expose an accessory.
573
+ *
574
+ * Delete the accessory delegate, but keep the HAP accessory, including
575
+ * the persisted context.
576
+ * The delegate will be re-created when the gateway is next polled.
577
+ * @params {string} id - The device ID.
578
+ * @params {boolean} expose - Set to `false` to blacklist the device.
579
+ */
580
+ reExposeAccessory (id) {
581
+ if (id === this.id) {
582
+ throw new RangeError(`${id}: gateway ID`)
583
+ }
584
+ if (this.accessoryById[id] == null) {
585
+ throw new RangeError(`${id}: unknown accessory ID`)
586
+ }
587
+ this.deleteAccessory(id, true)
588
+ this.pollNext = true
589
+ }
590
+
520
591
  /** Add the accessory for the device.
521
592
  * @params {string} id - The device ID.
522
593
  * @return {?DeconzAccessory} - The accessory delegate.
@@ -532,7 +603,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
532
603
  const device = this.deviceById[id]
533
604
  delete this.exposeErrorById[id]
534
605
  const { body } = device.resource
535
- this.log('%s: add accessory', body.name)
606
+ this.debug('%s: add accessory', body.name)
536
607
  let { serviceName } = device.resource
537
608
  if (DeconzAccessory[serviceName] == null) {
538
609
  // this.warn('%s: %s: not yet supported %s type', body.name, body.type, rtype)
@@ -549,19 +620,23 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
549
620
  return this.accessoryById[id]
550
621
  }
551
622
 
552
- /** Delete the accessory for the device.
623
+ /** Delete the accessory delegate and associated HomeKit accessory.
553
624
  * @params {string} id - The device ID.
625
+ * @params {boolean} [delegateOnly=false] - Delete the delegate, but keep the
626
+ * associated HomeKit accessory (including context).
554
627
  */
555
- deleteAccessory (id) {
628
+ deleteAccessory (id, delegateOnly = false) {
556
629
  if (id === this.id) {
557
630
  throw new RangeError(`${id}: gateway ID`)
558
631
  }
559
632
  if (this.accessoryById[id] != null) {
560
- this.log('%s: delete accessory', this.accessoryById[id].name)
561
633
  this.monitorResources(this.accessoryById[id], false)
562
- this.accessoryById[id].destroy()
634
+ this.log('%s: delete accessory', this.accessoryById[id].name)
635
+ this.accessoryById[id].destroy(delegateOnly)
563
636
  delete this.accessoryById[id]
564
- if (this.exposeErrorById[id] == null) {
637
+ if (this.exposeErrorById[id] != null) {
638
+ delete this.exposeErrorById[id]
639
+ } else if (!delegateOnly) {
565
640
  const id = Object.keys(this.exposeErrorById)[0]
566
641
  if (id != null) {
567
642
  this.log(
@@ -570,8 +645,6 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
570
645
  this.deleteAccessory(id)
571
646
  this.deleteService(id)
572
647
  }
573
- } else {
574
- delete this.exposeErrorById[id]
575
648
  }
576
649
  }
577
650
  }
@@ -608,86 +681,182 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
608
681
  this.deleteService(id)
609
682
  }
610
683
 
611
- /** Add a service to the gateway accessory to un-blacklist a device.
612
- * @params {string} id - The device ID.
613
- * @return {DeconzService.DeviceSettings} - The service delegate.
614
- */
615
- addService (id) {
616
- if (id === this.id) {
617
- throw new RangeError(`${id}: gateway ID`)
618
- }
619
- if (this.deviceById[id] == null) {
620
- throw new RangeError(`${id}: unknown device ID`)
684
+ // ===========================================================================
685
+
686
+ _deviceToMap (id, details = false) {
687
+ const device = this.deviceById[id]
688
+ if (device == null) {
689
+ return { status: 404 } // Not Found
621
690
  }
622
- if (this.serviceById[id] == null) {
623
- if (Object.keys(this.serviceById).length >= 97) {
624
- return null
625
- }
626
- const { resource, rpaths } = this.deviceById[id]
627
- const { body } = resource
628
- const service = new DeconzService.DeviceSettings(this, {
629
- name: body.name + ' Settings',
630
- subtype: id,
631
- resource: rpaths.join(', '),
632
- expose: this.context.settingsById[id].expose
633
- })
634
- this.serviceById[id] = service
691
+ const body = {
692
+ expose: details ? undefined : this.accessoryById[device.id] != null,
693
+ id: details ? device.id : undefined,
694
+ manufacturer: device.resource.manufacturer,
695
+ model: device.resource.model,
696
+ name: device.resource.body.name,
697
+ resources: device.rpaths,
698
+ settings: details
699
+ ? {
700
+ expose: this.accessoryById[device.id] != null,
701
+ outlet: undefined, // expose as _Outlet_
702
+ switch: undefined, // expose as _Switch
703
+ valve: undefined // expose as _Valve_
704
+ }
705
+ : undefined,
706
+ type: device.resource.rtype,
707
+ zigbee: device.zigbee
635
708
  }
636
- return this.serviceById[id]
709
+ return { status: 200, body }
637
710
  }
638
711
 
639
- /** Delete the service on the gateway accessory to un-blacklist a device.
640
- * @params {string} id - The device ID.
641
- */
642
- deleteService (id) {
643
- if (id === this.id) {
644
- throw new RangeError(`${id}: gateway ID`)
712
+ async onUiGet (path) {
713
+ this.debug('ui request: GET %s', path.join('/'))
714
+ if (path.length === 0) {
715
+ const body = {
716
+ host: this.values.host,
717
+ id: this.id,
718
+ manufacturer: this.values.manufacturer,
719
+ model: this.values.model,
720
+ name: this.name,
721
+ settings: {
722
+ autoExpose: this.values.autoExpose,
723
+ brightnessAdjustment: this.values.brightnessAdjustment * 100,
724
+ expose: this.values.expose,
725
+ heartrate: this.values.heartrate,
726
+ logLevel: this.values.logLevel,
727
+ periodicEvents: this.values.periodicEvents,
728
+ restart: this.values.restart,
729
+ search: this.values.search,
730
+ unlock: this.values.unlock
731
+ }
732
+ }
733
+ return { status: 200, body }
645
734
  }
646
- if (this.serviceById[id] != null) {
647
- this.serviceById[id].destroy()
648
- delete this.serviceById[id]
735
+ if (path[0] === 'accessories') {
736
+ if (path.length === 1) {
737
+ const body = {}
738
+ for (const id of Object.keys(this.accessoryById).sort()) {
739
+ body[id] = this.accessoryById[id].onUiGet().body
740
+ }
741
+ return { status: 200, body }
742
+ }
743
+ if (path.length === 2) {
744
+ const id = path[1]
745
+ if (this.accessoryById[id] == null) {
746
+ return { status: 404 } // Not Found
747
+ }
748
+ return this.accessoryById[id].onUiGet(true)
749
+ }
649
750
  }
650
- }
651
-
652
- // ===========================================================================
653
-
654
- async onUiGet (a) {
655
- this.debug('ui request: GET %s', a.join('/'))
656
- if (a.length === 0) {
657
- return {
658
- status: 200,
659
- body: {
660
- expose: this.service.values.expose,
661
- groups: this.service.values.groups,
662
- heartrate: this.service.values.heartrate,
663
- lights: this.service.values.lights,
664
- logLevel: this.service.values.logLevel,
665
- schedules: this.service.values.schedules,
666
- sensors: this.service.values.sensors
667
- // deviceByRidByRtype: this.deviceByRidByRtype
751
+ if (path[0] === 'devices') {
752
+ if (path.length === 1) {
753
+ const body = {}
754
+ for (const id of Object.keys(this.deviceById).sort()) {
755
+ body[id] = this._deviceToMap(id).body
668
756
  }
757
+ return { status: 200, body }
758
+ }
759
+ if (path.length === 2) {
760
+ return this._deviceToMap(path[1], true)
669
761
  }
670
762
  }
671
- if (a[0] !== 'accessories') {
672
- return { status: 403 } // Forbidden
763
+ return { status: 403 } // Forbidden
764
+ }
765
+
766
+ async onUiPut (path, body) {
767
+ this.debug('ui request: PUT %s %j', path.join('/'), body)
768
+ if (path.length === 0) {
769
+ return { status: 405 } // Method Not Allowed
673
770
  }
674
- if (a.length === 1) {
675
- return { status: 200, body: this.deviceByRidByRtype }
771
+ if (path[0] === 'settings') {
772
+ const settings = {}
773
+ const optionParser = new homebridgeLib.OptionParser(settings, true)
774
+ optionParser
775
+ .on('userInputError', (error) => {
776
+ this.warn(error)
777
+ })
778
+ .objectKey('autoExpose')
779
+ .intKey('brightnessAdjustment', 10, 100)
780
+ .boolKey('expose')
781
+ .intKey('heartrate', 1, 60)
782
+ .intKey('logLevel', 0, 3)
783
+ .boolKey('periodicEvents')
784
+ .boolKey('restart')
785
+ .boolKey('search')
786
+ .boolKey('unlock')
787
+ await optionParser.parse(body)
788
+
789
+ const responseBody = {}
790
+ for (const key in settings) {
791
+ switch (key) {
792
+ case 'autoExpose':
793
+ for (const rtype in settings[key]) {
794
+ if (this.values[key][rtype] == null) {
795
+ this.warn('ui error: %s.%s: invalid key', key, rtype)
796
+ continue
797
+ }
798
+ try {
799
+ settings[key][rtype] = homebridgeLib.OptionParser.toBool(
800
+ key + '.' + rtype, settings[key][rtype], true
801
+ )
802
+ } catch (error) {
803
+ this.warn(error)
804
+ continue
805
+ }
806
+ this.values[key][rtype] = settings[key][rtype]
807
+ if (responseBody[key] == null) {
808
+ responseBody[key] = {}
809
+ }
810
+ responseBody[key][rtype] = this.values[key][rtype]
811
+ }
812
+ break
813
+ case 'brightnessAdjustment':
814
+ this.values[key] = settings[key] / 100
815
+ responseBody[key] = this.values[key]
816
+ break
817
+ case 'expose':
818
+ case 'heartrate':
819
+ case 'logLevel':
820
+ case 'periodicEvents':
821
+ case 'restart':
822
+ case 'search':
823
+ case 'unlock':
824
+ this.values[key] = settings[key]
825
+ responseBody[key] = this.values[key]
826
+ break
827
+ default:
828
+ break
829
+ }
830
+ }
831
+ return { status: 200, body: responseBody }
676
832
  }
677
- if (this.deviceById[a[1]] == null) {
678
- return { status: 404 } // Not Found
833
+ if (path[0] === 'accessories') {
834
+ if (path.length === 3 && path[2] === 'settings') {
835
+ const id = path[1]
836
+ if (this.accessoryById[id] == null) {
837
+ return { status: 404 } // Not Found
838
+ }
839
+ return this.accessoryById[id].onUiPut(body)
840
+ }
841
+ return { status: 405 } // Method Not Allowed
679
842
  }
680
- if (a.length === 2) {
681
- return { status: 200, body: this.deviceById[a[1]] }
843
+ if (path[0] === 'devices') {
844
+ if (path.length === 3 && path[2] === 'settings') {
845
+ const id = path[1]
846
+ if (this.deviceById[id] == null) {
847
+ return { status: 404 } // Not Found
848
+ }
849
+ if (body.expose != null) {
850
+ this.exposeDevice(id, body.expose)
851
+ return { status: 200, body: { expose: body.expose } }
852
+ }
853
+ return { status: 200 }
854
+ }
855
+ return { status: 405 } // Method Not Allowed
682
856
  }
683
857
  return { status: 403 } // Forbidden
684
858
  }
685
859
 
686
- async onUiPut (a, body) {
687
- this.debug('ui request: PUT %s %j', a.join('/'), body)
688
- return { status: 501 } // Not Implented
689
- }
690
-
691
860
  // ===========================================================================
692
861
 
693
862
  /** Poll the gateway.
@@ -710,24 +879,24 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
710
879
  } else {
711
880
  const config = await this.client.get('/config')
712
881
  if (config.bridgeid === this.id && config.UTC == null) {
713
- this.service.values.expose = false
882
+ this.values.expose = false
714
883
  this.values.apiKey = null
715
884
  await this.wsClient.close()
716
885
  return
717
886
  }
718
887
  this.context.fullState.config = config
719
888
  if (
720
- this.service.values.lights || this.nDevicesByRtype.lights > 0 ||
721
- this.service.values.sensors || this.nDevicesByRtype.sensors > 0
889
+ this.values.autoExpose.lights || this.nDevicesByRtype.lights > 0 ||
890
+ this.values.autoExpose.sensors || this.nDevicesByRtype.sensors > 0
722
891
  ) {
723
892
  this.context.fullState.lights = await this.client.get('/lights')
724
893
  this.context.fullState.sensors = await this.client.get('/sensors')
725
894
  }
726
- if (this.service.values.groups || this.nDevicesByRtype.groups > 0) {
895
+ if (this.values.autoExpose.groups || this.nDevicesByRtype.groups > 0) {
727
896
  this.context.fullState.groups = await this.client.get('/groups')
728
897
  this.context.fullState.groups[0] = await this.client.get('/groups/0')
729
898
  }
730
- if (this.service.values.schedules) {
899
+ if (this.values.autoExpose.schedules) {
731
900
  this.context.fullState.schedules = await this.client.get('/schedules')
732
901
  }
733
902
  }
@@ -871,7 +1040,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
871
1040
  ) {
872
1041
  delete this.context.settingsById[id]
873
1042
  this.deleteAccessory(id)
874
- this.deleteService(id)
1043
+ // this.deleteService(id)
875
1044
  changed = true
876
1045
  } else {
877
1046
  /** Emitted when the gateway has been polled.
@@ -883,43 +1052,23 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
883
1052
  } catch (error) { this.error(error) }
884
1053
  }
885
1054
 
886
- this.vdebug('analysing services...')
887
- for (const id in this.serviceById) {
888
- try {
889
- if (
890
- this.deviceById[id] == null ||
891
- !this.values.rtypes.includes(this.deviceById[id].resource.rtype)
892
- ) {
893
- delete this.exposeErrorById[id]
894
- this.deleteService(id)
895
- changed = true
896
- }
897
- } catch (error) { this.error(error) }
898
- }
899
-
900
- for (const rtype of this.values.rtypes) {
1055
+ for (const rtype of rtypes) {
901
1056
  this.vdebug('analysing %s devices...', rtype)
902
1057
  const rids = Object.keys(this.deviceByRidByRtype[rtype]).sort()
903
1058
  for (const rid of rids) {
904
1059
  try {
905
1060
  const { id } = this.deviceByRidByRtype[rtype][rid]
906
1061
  if (this.context.settingsById[id] == null) {
907
- this.context.settingsById[id] = { expose: true }
1062
+ this.context.settingsById[id] = {
1063
+ expose: this.values.autoExpose[rtype]
1064
+ }
908
1065
  }
909
1066
  if (this.context.settingsById[id].expose) {
910
1067
  if (this.accessoryById[id] == null) {
911
1068
  this.addAccessory(id)
912
1069
  changed = true
913
1070
  }
914
- if (this.serviceById[id] != null) {
915
- this.deleteService(id)
916
- changed = true
917
- }
918
1071
  } else {
919
- if (this.serviceById[id] == null) {
920
- this.addService(id)
921
- changed = true
922
- }
923
1072
  if (this.accessoryById[id] != null) {
924
1073
  this.deleteAccessory(id)
925
1074
  changed = true