homebridge-deconz 0.1.10 → 0.1.11

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.
package/cli/deconz.js CHANGED
@@ -514,10 +514,7 @@ class Main extends CommandLineTool {
514
514
 
515
515
  this.bridgeid = this.gatewayConfig.bridgeid
516
516
  if (this.clargs.options.apiKey == null) {
517
- if (
518
- this.gateways[this.bridgeid] != null &&
519
- this.gateways[this.bridgeid].apiKey != null
520
- ) {
517
+ if (this.gateways[this.bridgeid]?.apiKey != null) {
521
518
  this.clargs.options.apiKey = this.gateways[this.bridgeid].apiKey
522
519
  } else if (process.env.DECONZ_API_KEY != null) {
523
520
  this.clargs.options.apiKey = process.env.DECONZ_API_KEY
@@ -32,8 +32,7 @@ async function showFormPluginConfig () {
32
32
  console.log('%s: cachedAccessories: %o', config.name, cachedAccessories)
33
33
  const cachedGateways = cachedAccessories.filter((accessory) => {
34
34
  return accessory.plugin === 'homebridge-deconz' &&
35
- accessory.context != null &&
36
- accessory.context.className === 'Gateway'
35
+ accessory.context?.className === 'Gateway'
37
36
  })
38
37
  const result = {}
39
38
  for (const gateway of cachedGateways) {
@@ -207,8 +206,7 @@ async function showFormGateways (gateway) {
207
206
  const cachedAccessories = await homebridge.getCachedAccessories()
208
207
  const cachedGateways = cachedAccessories.filter((accessory) => {
209
208
  return accessory.plugin === 'homebridge-deconz' &&
210
- accessory.context != null &&
211
- accessory.context.className === 'Gateway'
209
+ accessory.context?.className === 'Gateway'
212
210
  })
213
211
  const result = {}
214
212
  for (const gateway of cachedGateways) {
@@ -249,7 +247,7 @@ async function showFormGateways (gateway) {
249
247
  // layout: null,
250
248
  // form: null
251
249
  // }, {
252
- // gateway: gateway != null ? gateway : gateways[0]
250
+ // gateway: gateway ?? gateways[0]
253
251
  // }, 'Gateway Settings', 'Homebridge Settings')
254
252
  const form = homebridge.createForm({
255
253
  footerDisplay: 'For a detailed description, see the [wiki](https://github.com/ebaauw/homebridge-deconz/wiki/Configuration).',
@@ -5,13 +5,12 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const { Colour, OptionParser } = require('homebridge-lib')
8
+ const { OptionParser } = require('homebridge-lib')
9
9
  const Deconz = require('../Deconz')
10
10
  const DeconzAccessory = require('../DeconzAccessory')
11
11
  const DeconzService = require('../DeconzService')
12
12
 
13
13
  const { toInstance, toInt, toObject, toString } = OptionParser
14
- const { defaultGamut } = Colour
15
14
  const { buttonEvent } = Deconz.ApiClient
16
15
  const { SINGLE, DOUBLE, LONG } = DeconzService.Button
17
16
  const rtypes = ['lights', 'sensors', 'groups']
@@ -54,9 +53,9 @@ class Resource {
54
53
  toString('uniqueid', uniqueid, true)
55
54
  const a = uniqueid.replace(/:/g, '').toUpperCase().split('-')
56
55
  return {
57
- mac: a.length > 0 ? a[0] : null,
58
- endpoint: a.length > 1 ? a[1] : null,
59
- cluster: a.length > 2 ? a[2] : null
56
+ mac: a?.[0],
57
+ endpoint: a?.[1],
58
+ cluster: a?.[2]
60
59
  }
61
60
  }
62
61
 
@@ -130,6 +129,11 @@ class Resource {
130
129
  * @type {boolean}
131
130
  */
132
131
  this.zigbee = true
132
+ } else if (this.isDeconzClip()) {
133
+ const a = body.uniqueid.split('-')
134
+ this.id = gateway.id + '-M' + a?.[0]
135
+ this.subtype = a?.[1]
136
+ this.zigbee = false
133
137
  } else {
134
138
  this.subtype = rtype[0].toUpperCase() + rid
135
139
  this.id = gateway.id + '-' + this.subtype
@@ -166,58 +170,16 @@ class Resource {
166
170
  ? body.swversion == null ? '0.0.0' : body.swversion
167
171
  : gateway.values.software
168
172
 
169
- switch (this.serviceName) {
170
- case 'Light': {
171
- if (body.action != null) {
172
- Object.assign(body.state, body.action)
173
- delete body.state.on
174
- }
175
- this.capabilities = {
176
- on: body.state.on !== undefined,
177
- bri: body.state.bri !== undefined,
178
- ct: body.state.ct !== undefined,
179
- ctMax: (body.ctmax != null && body.ctmax !== 0 && body.ctmax !== 65535)
180
- ? body.ctmax
181
- : 500,
182
- ctMin: (body.ctmin != null && body.ctmin !== 0)
183
- ? body.ctmin
184
- : 153,
185
- xy: body.state.xy !== undefined,
186
- gamut: defaultGamut,
187
- alert: body.state.alert !== undefined,
188
- colorLoop: body.state.effect !== undefined
189
- }
190
- if (body.capabilities != null) {
191
- const cap = body.capabilities
192
- if (cap.color != null) {
193
- if (cap.color.ct != null) {
194
- this.capabilities.ctMax = cap.color.ct.max
195
- this.capabilities.ctMin = cap.color.ct.min
196
- }
197
- if (cap.color.effects != null && cap.color.effects.length > 2) {
198
- this.capabilities.effects = cap.color.effects.slice(2)
199
- }
200
- if (cap.color.xy != null) {
201
- this.capabilities.gamut = {
202
- r: cap.color.xy.red,
203
- g: cap.color.xy.green,
204
- b: cap.color.xy.blue
205
- }
206
- }
207
- }
208
- }
209
- break
210
- }
211
- case 'WarningDevice':
212
- this.capabilities = {}
213
- break
214
- case 'WindowCovering':
215
- this.capabilities = {}
216
- break
217
- default:
218
- this.capabilities = {}
219
- break
220
- }
173
+ /** The name of the {@link DeconzService} subclass of the delegate of the
174
+ * corresponding HomeKit service, or `null` for unsupported and unknown
175
+ * resources.
176
+ *
177
+ * This is derived from the resource type and `type` in the resource body.
178
+ * @type {string}
179
+ */
180
+ this.serviceName = this._serviceName
181
+
182
+ this.capabilities = {}
221
183
 
222
184
  const f = 'patch' + this.serviceName
223
185
  if (typeof this[f] === 'function') {
@@ -230,8 +192,12 @@ class Resource {
230
192
  * @type {integer}
231
193
  */
232
194
  get prio () {
233
- if (this.rtype === 'groups') return -1
234
- if (this.rtype === 'lights') return 0xFF - this.endpoint
195
+ if (this.rtype === 'groups') {
196
+ return -1
197
+ }
198
+ if (this.rtype === 'lights' || this.isDeconzClip()) {
199
+ return 0xFF - this.endpoint
200
+ }
235
201
  return sensorsPrios.indexOf(this.serviceName)
236
202
  }
237
203
 
@@ -242,14 +208,7 @@ class Resource {
242
208
  */
243
209
  get rpath () { return '/' + this.rtype + '/' + this.rid }
244
210
 
245
- /** The name of the {@link DeconzService} subclass of the delegate of the
246
- * corresponding HomeKit service, or `null` for unsupported and unknown
247
- * resources.
248
- *
249
- * This is derived from the resource type and `type` in the resource body.
250
- * @type {string}
251
- */
252
- get serviceName () {
211
+ get _serviceName () {
253
212
  if (this.rtype === 'groups') {
254
213
  return 'Light'
255
214
  } else if (this.rtype === 'lights') {
@@ -267,11 +226,11 @@ class Resource {
267
226
  // case 'Door Lock': return null
268
227
  // case 'Door Lock Unit': return null
269
228
  case 'Fan': return 'Light'
270
- case 'On/Off light switch': return 'Light'
229
+ case 'On/Off light switch': return 'Switch'
271
230
  case 'On/Off light': return 'Light'
272
- case 'On/Off output': return 'Light'
273
- case 'On/Off plug-in unit': return 'Light'
274
- case 'Smart plug': return 'Light'
231
+ case 'On/Off output': return 'Outlet'
232
+ case 'On/Off plug-in unit': return 'Outlet'
233
+ case 'Smart plug': return 'Outlet'
275
234
  case 'Configuration tool': return ''
276
235
  case 'Range extender': return ''
277
236
  case 'Warning device': return 'WarningDevice'
@@ -280,55 +239,34 @@ class Resource {
280
239
  default: return null
281
240
  }
282
241
  } else { // (this.rtype === 'sensors')
283
- switch (this.body.type) {
284
- case 'CLIPAirPurifier':
285
- case 'ZHAAirPurifier': return 'AirPurifier'
286
- case 'ZHAAirQuality':
287
- case 'CLIPAirQuality': return 'AirQuality'
288
- case 'ZHAAlarm':
289
- case 'CLIPAlarm': return 'Alarm'
290
- case 'ZHABattery':
291
- case 'CLIPBattery': return 'Battery'
292
- case 'ZHACarbonMonoxide':
293
- case 'CLIPCarbonMonoxide': return 'CarbonMonoxide'
294
- case 'ZHAConsumption':
295
- case 'CLIPConsumption': return 'Consumption'
296
- // case 'ZHADoorLock':
297
- // case 'CLIPDoorLock': return null
242
+ const type = /^(CLIP|ZHA|ZGP)?([A-Za-z]*)$/.exec(this.body.type)?.[2]
243
+ switch (type) {
244
+ case 'AirPurifier': return 'AirPurifier'
245
+ case 'AirQuality': return 'AirQuality'
246
+ case 'Alarm': return 'Alarm'
247
+ case 'Battery': return 'Battery'
248
+ case 'CarbonMonoxide': return 'CarbonMonoxide'
249
+ case 'Consumption': return 'Consumption'
250
+ // case 'DoorLock': return null
298
251
  case 'Daylight': return 'Daylight'
299
- case 'ZHAFire':
300
- case 'CLIPFire': return 'Smoke'
301
- case 'CLIPGenericFlag': return 'Flag'
302
- case 'CLIPGenericStatus': return 'Status'
303
- case 'ZHAHumidity':
304
- case 'CLIPHumidity': return 'Humidity'
305
- case 'ZHALightLevel':
306
- case 'CLIPLightLevel': return 'LightLevel'
307
- // case 'ZHAMoisture':
308
- // case 'CLIPMoisture': return null
309
- case 'ZHAOpenClose':
310
- case 'CLIPOpenClose': return 'Contact'
311
- case 'ZHAPower':
312
- case 'CLIPPower': return 'Power'
313
- case 'ZHAPresence':
314
- case 'CLIPPresence': return 'Motion'
315
- case 'ZHAPressure':
316
- case 'CLIPPressure': return 'AirPressure'
317
- case 'ZHARelativeRotary': return 'Switch'
318
- case 'ZHASpectral': return ''
319
- case 'ZGPSwitch':
320
- case 'ZHASwitch':
321
- case 'CLIPSwitch': return 'Switch'
322
- case 'ZHATemperature':
323
- case 'CLIPTemperature': return 'Temperature'
324
- case 'ZHAThermostat':
325
- case 'CLIPThermostat': return 'Thermostat'
326
- case 'ZHATime':
327
- case 'CLIPTime': return ''
328
- case 'ZHAVibration':
329
- case 'CLIPVibration': return 'Motion'
330
- case 'ZHAWater':
331
- case 'CLIPWater': return 'Leak'
252
+ case 'Fire': return 'Smoke'
253
+ case 'GenericFlag': return 'Flag'
254
+ case 'GenericStatus': return 'Status'
255
+ case 'Humidity': return 'Humidity'
256
+ case 'LightLevel': return 'LightLevel'
257
+ case 'Moisture': return 'Humidity'
258
+ case 'OpenClose': return 'Contact'
259
+ case 'Power': return 'Power'
260
+ case 'Presence': return 'Motion'
261
+ case 'Pressure': return 'AirPressure'
262
+ case 'RelativeRotary': return 'Label'
263
+ case 'Spectral': return ''
264
+ case 'Switch': return 'Label'
265
+ case 'Temperature': return 'Temperature'
266
+ case 'Thermostat': return 'Thermostat'
267
+ case 'Time': return ''
268
+ case 'Vibration': return 'Motion'
269
+ case 'Water': return 'Leak'
332
270
  default: return null
333
271
  }
334
272
  }
@@ -389,63 +327,48 @@ class Resource {
389
327
  }
390
328
  }
391
329
 
330
+ /** Check whether resource is a CLIP sensor for Homebridge deCONZ.
331
+ * For these, we use the following conventions:
332
+ * - Use `uniqueid` to indicate device ID and subtype;
333
+ * - Use `swversion` to indicate readonly and range for `Flag` and `Status`.
334
+ */
335
+ isDeconzClip () {
336
+ return this.rtype === 'sensors' &&
337
+ this.body.type.startsWith('CLIP') &&
338
+ this.body.modelid === this.body.type && (
339
+ this.body.manufacturername === 'homebridge-deconz' ||
340
+ this.body.manufacturername === 'homebridge-hue'
341
+ )
342
+ }
343
+
392
344
  /** Patch a resource corresponding to a `Flag` service.
393
- * @param {DeconzAccessory.Gateway} gateway - The gateway.
394
345
  */
395
- patchFlag (gateway) {
396
- if (
397
- this.body.manufacturername === 'homebridge-hue' &&
398
- this.body.modelid === 'CLIPGenericFlag' &&
399
- this.body.swversion === '0'
400
- ) {
346
+ patchFlag () {
347
+ if (this.isDeconzClip() && this.body.swversion === '0') {
401
348
  this.capabilities.readonly = true
402
349
  }
403
350
  }
404
351
 
405
- /** Patch a resource corresponding to a `Flag` service.
406
- * @param {DeconzAccessory.Gateway} gateway - The gateway.
352
+ /** Patch a resource corresponding to a `Status` service.
407
353
  */
408
- patchStatus (gateway) {
409
- if (
410
- this.body.manufacturername === 'homebridge-hue' &&
411
- this.body.modelid === 'CLIPGenericStatus'
412
- ) {
354
+ patchStatus () {
355
+ if (this.isDeconzClip()) {
413
356
  const a = this.body.swversion.split(',')
414
357
  const min = parseInt(a[0])
415
358
  const max = parseInt(a[1])
416
- const step = parseInt(a[2])
417
- // Eve 3.1 displays the following controls, depending on the properties:
418
- // 1. {minValue: 0, maxValue: 1, minStep: 1} switch
419
- // 2. {minValue: a, maxValue: b, minStep: 1}, 1 < b - a <= 20 down|up
420
- // 3. {minValue: a, maxValue: b}, (a, b) != (0, 1) slider
421
- // 4. {minValue: a, maxValue: b, minStep: 1}, b - a > 20 slider
422
- // Avoid the following bugs:
423
- // 5. {minValue: 0, maxValue: 1} nothing
424
- // 6. {minValue: a, maxValue: b, minStep: 1}, b - a = 1 switch*
425
- // *) switch sends values 0 and 1 instead of a and b;
426
359
  if (min === 0 && max === 0) {
427
360
  this.capabilities.readonly = true
428
361
  } else if (min >= -127 && max <= 127 && min < max) {
429
- if (min === 0 && max === 1) {
430
- // Workaround Eve bug (case 5 above).
431
- this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
432
- } else if (max - min === 1) {
433
- // Workaround Eve bug (case 6 above).
434
- this.capabilities.props = { minValue: min, maxValue: max }
435
- } else if (step !== 1) {
436
- // Default to slider for backwards compatibility.
437
- this.capabilities.props = { minValue: min, maxValue: max }
438
- } else {
439
- this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
440
- }
362
+ this.capabilities.min = min
363
+ this.capabilities.max = max
441
364
  }
442
365
  }
443
366
  }
444
367
 
445
- /** Patch a resource corresponding to a `Switch` service.
368
+ /** Patch a resource corresponding to a `Label` service.
446
369
  * @param {DeconzAccessory.Gateway} gateway - The gateway.
447
370
  */
448
- patchSwitch (gateway) {
371
+ patchLabel (gateway) {
449
372
  const buttons = []
450
373
  let dots = false
451
374
  switch (this.manufacturer) {
@@ -536,6 +459,15 @@ class Resource {
536
459
  buttons.push([3, 'Turn Left', SINGLE])
537
460
  }
538
461
  break
462
+ case 'SYMFONISK sound remote gen2':
463
+ buttons.push([1, 'Play', SINGLE])
464
+ buttons.push([2, 'Plus', SINGLE | LONG, true])
465
+ buttons.push([3, 'Minus', SINGLE | LONG, true])
466
+ buttons.push([4, 'Previous', SINGLE])
467
+ buttons.push([5, 'Next', SINGLE])
468
+ buttons.push([6, 'One Dot', SINGLE | DOUBLE | LONG])
469
+ buttons.push([7, 'Two Dots', SINGLE | DOUBLE | LONG])
470
+ break
539
471
  case 'TRADFRI SHORTCUT Button':
540
472
  buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
541
473
  break
@@ -1124,9 +1056,8 @@ class Resource {
1124
1056
  }
1125
1057
 
1126
1058
  /** Patch a resource corresponding to a `Thermostat` service.
1127
- * @param {DeconzAccessory.Gateway} gateway - The gateway.
1128
1059
  */
1129
- patchThermostat (gateway) {
1060
+ patchThermostat () {
1130
1061
  if (this.manufacturer === 'ELKO' && this.model === 'Super TR') {
1131
1062
  this.capabilities.heatValue = 'heat'
1132
1063
  } else {
@@ -1135,9 +1066,8 @@ class Resource {
1135
1066
  }
1136
1067
 
1137
1068
  /** Patch a resource corresponding to a `WindowCovering` service.
1138
- * @param {DeconzAccessory.Gateway} gateway - The gateway.
1139
1069
  */
1140
- patchWindowCovering (gateway) {
1070
+ patchWindowCovering () {
1141
1071
  if (this.manufacturer === 'LUMI' && this.model === 'lumi.curtain.acn002') {
1142
1072
  this.capabilities.maxSpeed = 2
1143
1073
  this.capabilities.positionChange = true
@@ -13,6 +13,12 @@ const Deconz = require('../Deconz')
13
13
  const DeconzAccessory = require('../DeconzAccessory')
14
14
  const DeconzService = require('../DeconzService')
15
15
 
16
+ const migration = {
17
+ name: 'homebridge-deconz',
18
+ description: 'migration',
19
+ classid: 1
20
+ }
21
+
16
22
  const rtypes = ['lights', 'sensors', 'groups']
17
23
 
18
24
  const periodicEvents = [
@@ -516,9 +522,7 @@ class Gateway extends AccessoryDelegate {
516
522
  }
517
523
  try {
518
524
  try {
519
- if (this.context.migration != null) {
520
- await this.client.delete(this.context.migration)
521
- }
525
+ await this.deleteMigration()
522
526
  await this.client.deleteApiKey()
523
527
  } catch (error) {}
524
528
  this.values.apiKey = null
@@ -531,7 +535,6 @@ class Gateway extends AccessoryDelegate {
531
535
  this.exposeErrors = {}
532
536
  this.context.settingsById = {}
533
537
  this.context.fullState = null
534
- this.context.migration = null
535
538
  this.values.logLevel = 2
536
539
  } catch (error) { this.error(error) }
537
540
  }
@@ -669,6 +672,60 @@ class Gateway extends AccessoryDelegate {
669
672
  this.deleteService(id)
670
673
  }
671
674
 
675
+ /** Assert that migration resourcelink exists and is valid.
676
+ */
677
+ async checkMigration () {
678
+ if (this.context.migration != null) {
679
+ try {
680
+ const response = await this.client.get(this.context.migration)
681
+ if (
682
+ response.name !== migration.name ||
683
+ response.description !== migration.description ||
684
+ response.classid !== migration.classid ||
685
+ response.owner !== this.client.apiKey
686
+ ) {
687
+ // not my migration resourcelink
688
+ this.warn('%s: migration resourcelink no longer valid', this.context.migration)
689
+ this.context.migration = null
690
+ }
691
+ } catch (error) {
692
+ if (error.statusCode === 404) {
693
+ this.warn('%s: migration resourcelink no longer exists', this.context.migration)
694
+ this.context.migration = null
695
+ }
696
+ }
697
+ }
698
+ }
699
+
700
+ /** Create or update migration resourcelink.
701
+ */
702
+ async updateMigration () {
703
+ await this.checkMigration()
704
+ if (this.context.migration == null) {
705
+ const response = await this.client.post('/resourcelinks', {
706
+ name: migration.name,
707
+ description: migration.description,
708
+ classid: migration.classid,
709
+ links: Object.keys(this.accessoryByRpath).sort()
710
+ })
711
+ this.context.migration = '/resourcelinks/' + response.success.id
712
+ } else {
713
+ await this.client.put(this.context.migration, {
714
+ links: Object.keys(this.accessoryByRpath).sort()
715
+ })
716
+ }
717
+ }
718
+
719
+ /** Delete migration resourcelink.
720
+ */
721
+ async deleteMigration () {
722
+ await this.checkMigration()
723
+ if (this.context.migration != null) {
724
+ await this.client.delete(this.context.migration)
725
+ this.context.migration = null
726
+ }
727
+ }
728
+
672
729
  // ===========================================================================
673
730
 
674
731
  _deviceToMap (id, details = false) {
@@ -733,7 +790,7 @@ class Gateway extends AccessoryDelegate {
733
790
  return { status: 200, body }
734
791
  }
735
792
  if (path.length === 2) {
736
- const id = path[1]
793
+ const id = path[1].replace(/:/g, '').toUpperCase()
737
794
  if (this.accessoryById[id] == null) {
738
795
  return { status: 404 } // Not Found
739
796
  }
@@ -749,7 +806,7 @@ class Gateway extends AccessoryDelegate {
749
806
  return { status: 200, body }
750
807
  }
751
808
  if (path.length === 2) {
752
- return this._deviceToMap(path[1], true)
809
+ return this._deviceToMap(path[1].replace(/:/g, '').toUpperCase(), true)
753
810
  }
754
811
  }
755
812
  return { status: 403 } // Forbidden
@@ -808,7 +865,7 @@ class Gateway extends AccessoryDelegate {
808
865
  return { status: 405 } // Method Not Allowed
809
866
  }
810
867
  if (path.length === 3 && path[2] === 'settings') {
811
- const id = path[1]
868
+ const id = path[1].replace(/:/g, '').toUpperCase()
812
869
  if (this.accessoryById[id] == null) {
813
870
  return { status: 404 } // Not Found
814
871
  }
@@ -820,7 +877,7 @@ class Gateway extends AccessoryDelegate {
820
877
  return { status: 405 } // Method Not Allowed
821
878
  }
822
879
  if (path.length === 3 && path[2] === 'settings') {
823
- const id = path[1]
880
+ const id = path[1].replace(/:/g, '').toUpperCase()
824
881
  if (this.deviceById[id] == null) {
825
882
  return { status: 404 } // Not Found
826
883
  }
@@ -1060,19 +1117,7 @@ class Gateway extends AccessoryDelegate {
1060
1117
  }
1061
1118
 
1062
1119
  if (changed) {
1063
- if (this.context.migration == null) {
1064
- const response = await this.client.post('/resourcelinks', {
1065
- name: 'homebridge-deconz',
1066
- description: 'migration',
1067
- classid: 1,
1068
- links: Object.keys(this.accessoryByRpath).sort()
1069
- })
1070
- this.context.migration = '/resourcelinks/' + response.success.id
1071
- } else {
1072
- await this.client.put(this.context.migration, {
1073
- links: Object.keys(this.accessoryByRpath).sort()
1074
- })
1075
- }
1120
+ await this.updateMigration()
1076
1121
  this.identify()
1077
1122
  }
1078
1123
  }
@@ -25,7 +25,7 @@ class Light extends DeconzAccessory {
25
25
 
26
26
  this.addPropertyDelegate({
27
27
  key: 'serviceName',
28
- value: device.resource.capabilities.bri ? 'Light' : 'Outlet'
28
+ value: device.resource.serviceName
29
29
  })
30
30
 
31
31
  this.service = this.createService(device.resource, {
@@ -65,7 +65,7 @@ class Light extends DeconzAccessory {
65
65
  silent: true
66
66
  })
67
67
  }
68
- if (this.serviceByServiceName.Consumption != null) {
68
+ if (this.servicesByServiceName.Consumption?.[0] != null) {
69
69
  params.totalConsumptionDelegate = this.service.characteristicDelegate('totalConsumption')
70
70
  if (this.service.values.consumption === undefined) {
71
71
  // Power to be computed by history if not exposed by device
@@ -75,7 +75,7 @@ class Light extends DeconzAccessory {
75
75
  unit: ' W'
76
76
  })
77
77
  }
78
- } else if (this.serviceByServiceName.Power != null) {
78
+ } else if (this.servicesByServiceName.Power?.[0] != null) {
79
79
  params.consumptionDelegate = this.service.characteristicDelegate('consumption')
80
80
  // Total Consumption to be computed by history
81
81
  params.computedTotalConsumptionDelegate = this.service.addCharacteristicDelegate({
@@ -3,6 +3,11 @@
3
3
  //
4
4
  // Homebridge plugin for deCONZ.
5
5
 
6
+ // Keep separate for Eve History
7
+ // Switch/Outlet/Lightbulb
8
+ // Stateless Programmable Switch (Eve button)
9
+ // Sensors
10
+
6
11
  'use strict'
7
12
 
8
13
  const { ServiceDelegate } = require('homebridge-lib')
@@ -51,8 +56,8 @@ class Sensor extends DeconzAccessory {
51
56
  }
52
57
 
53
58
  const params = {}
54
- if (this.serviceByServiceName.Contact != null) {
55
- const service = this.serviceByServiceName.Contact
59
+ if (this.servicesByServiceName.Contact?.length === 1) {
60
+ const service = this.servicesByServiceName.Contact[0]
56
61
  params.contactDelegate = service.characteristicDelegate('contact')
57
62
  params.lastContactDelegate = service.addCharacteristicDelegate({
58
63
  key: 'lastActivation',
@@ -66,8 +71,8 @@ class Sensor extends DeconzAccessory {
66
71
  silent: true
67
72
  })
68
73
  }
69
- if (this.serviceByServiceName.Motion != null) {
70
- const service = this.serviceByServiceName.Motion
74
+ if (this.servicesByServiceName.Motion?.length === 1) {
75
+ const service = this.servicesByServiceName.Motion[0]
71
76
  params.motionDelegate = service.characteristicDelegate('motion')
72
77
  params.lastMotionDelegate = service.addCharacteristicDelegate({
73
78
  key: 'lastActivation',
@@ -75,34 +80,34 @@ class Sensor extends DeconzAccessory {
75
80
  silent: true
76
81
  })
77
82
  }
78
- if (this.serviceByServiceName.LightLevel != null) {
79
- const service = this.serviceByServiceName.LightLevel
83
+ if (this.servicesByServiceName.LightLevel?.length === 1) {
84
+ const service = this.servicesByServiceName.LightLevel[0]
80
85
  params.lightLevelDelegate = service.characteristicDelegate('lightLevel')
81
86
  }
82
- if (this.serviceByServiceName.Daylight != null) {
83
- const service = this.serviceByServiceName.Daylight
87
+ if (this.servicesByServiceName.Daylight?.length === 1) {
88
+ const service = this.servicesByServiceName.Daylight[0]
84
89
  params.lightLevelDelegate = service.characteristicDelegate('lightLevel')
85
90
  }
86
- if (this.serviceByServiceName.Temperature != null) {
87
- const service = this.serviceByServiceName.Temperature
91
+ if (this.servicesByServiceName.Temperature?.length === 1) {
92
+ const service = this.servicesByServiceName.Temperature[0]
88
93
  params.temperatureDelegate = service.characteristicDelegate('temperature')
89
94
  }
90
- if (this.serviceByServiceName.Humidity != null) {
91
- const service = this.serviceByServiceName.Humidity
95
+ if (this.servicesByServiceName.Humidity?.length === 1) {
96
+ const service = this.servicesByServiceName.Humidity[0]
92
97
  params.humidityDelegate = service.characteristicDelegate('humidity')
93
98
  }
94
- if (this.serviceByServiceName.AirPressure != null) {
95
- const service = this.serviceByServiceName.AirPressure
99
+ if (this.servicesByServiceName.AirPressure?.length === 1) {
100
+ const service = this.servicesByServiceName.AirPressure[0]
96
101
  params.airPressureDelegate = service.characteristicDelegate('airPressure')
97
102
  }
98
- if (this.serviceByServiceName.AirQuality != null) {
99
- const service = this.serviceByServiceName.AirQuality
103
+ if (this.servicesByServiceName.AirQuality?.length === 1) {
104
+ const service = this.servicesByServiceName.AirQuality[0]
100
105
  if (service.characteristicDelegate('vocDensity') != null) {
101
106
  params.vocDensityDelegate = service.characteristicDelegate('vocDensity')
102
107
  }
103
108
  }
104
- if (this.serviceByServiceName.Flag != null) {
105
- const service = this.serviceByServiceName.Flag
109
+ if (this.servicesByServiceName.Flag != null) {
110
+ const service = this.servicesByServiceName.Flag[0]
106
111
  params.switchOnDelegate = service.characteristicDelegate('on')
107
112
  params.lastSwitchOnDelegate = service.addCharacteristicDelegate({
108
113
  key: 'lastActivation',
@@ -19,7 +19,9 @@ const { SINGLE, DOUBLE, LONG } = DeconzService.Button
19
19
  class DeconzAccessory extends AccessoryDelegate {
20
20
  static get Light () { return require('./Light') }
21
21
  static get Gateway () { return require('./Gateway') }
22
+ static get Outlet () { return DeconzAccessory.Light }
22
23
  static get Sensor () { return require('./Sensor') }
24
+ static get Switch () { return DeconzAccessory.Light }
23
25
  static get WarningDevice () { return require('./WarningDevice') }
24
26
  static get WindowCovering () { return require('./WindowCovering') }
25
27
 
@@ -29,7 +31,6 @@ class DeconzAccessory extends AccessoryDelegate {
29
31
  * @param {Accessory.Category} category - The HomeKit accessory category.
30
32
  */
31
33
  constructor (gateway, device, category) {
32
- // TODO device settings
33
34
  super(gateway.platform, {
34
35
  id: device.id,
35
36
  name: device.resource.body.name,
@@ -44,7 +45,7 @@ class DeconzAccessory extends AccessoryDelegate {
44
45
 
45
46
  this.serviceByRpath = {}
46
47
  this.serviceBySubtype = {}
47
- this.serviceByServiceName = {}
48
+ this.servicesByServiceName = {}
48
49
 
49
50
  /** The gateway.
50
51
  * @type {DeconzAccessory.Gateway}
@@ -140,27 +141,27 @@ class DeconzAccessory extends AccessoryDelegate {
140
141
 
141
142
  let service
142
143
  if (params.serviceName === 'AirQuality') {
143
- service = this.serviceByServiceName.AirQuality
144
+ service = this.servicesByServiceName.AirQuality?.[0]
144
145
  if (service != null) {
145
146
  service.addResource(resource)
146
147
  }
147
148
  } else if (params.serviceName === 'Battery') {
148
- service = this.serviceByServiceName.Battery
149
+ service = this.servicesByServiceName.Battery?.[0]
149
150
  } else if (params.serviceName === 'Consumption') {
150
- service = this.serviceByServiceName.Outlet ||
151
- this.serviceByServiceName.Light ||
152
- this.serviceByServiceName.Power
151
+ service = this.servicesByServiceName.Outlet?.[0] ||
152
+ this.servicesByServiceName.Light?.[0] ||
153
+ this.servicesByServiceName.Power?.[0]
153
154
  if (service != null) {
154
155
  service.addResource(resource)
155
156
  }
156
157
  } else if (params.serviceName === 'Power') {
157
- service = this.serviceByServiceName.Outlet ||
158
- this.serviceByServiceName.Light ||
159
- this.serviceByServiceName.Consumption
158
+ service = this.servicesByServiceName.Outlet?.[0] ||
159
+ this.servicesByServiceName.Light?.[0] ||
160
+ this.servicesByServiceName.Consumption?.[0]
160
161
  if (service != null) {
161
162
  service.addResource(resource)
162
163
  }
163
- } else if (params.serviceName === 'Switch') {
164
+ } else if (params.serviceName === 'Label') {
164
165
  // Default button
165
166
  if (resource.capabilities.buttons == null) {
166
167
  this.warn(
@@ -176,9 +177,9 @@ class DeconzAccessory extends AccessoryDelegate {
176
177
  resource.capabilities.namespace =
177
178
  this.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
178
179
  }
179
- service = this.serviceByServiceName.Switch
180
+ service = this.servicesByServiceName.Label?.[0]
180
181
  if (service == null) {
181
- service = new DeconzService.Switch(this, resource, {
182
+ service = new DeconzService.Label(this, resource, {
182
183
  primaryService: params.primaryService
183
184
  })
184
185
  }
@@ -191,17 +192,16 @@ class DeconzAccessory extends AccessoryDelegate {
191
192
  }
192
193
  this.serviceBySubtype[resource.subtype] = service
193
194
  this.serviceByRpath[resource.rpath] = service
194
- if (this.serviceByServiceName[params.serviceName] == null) {
195
- this.serviceByServiceName[params.serviceName] = service
195
+ if (this.servicesByServiceName[params.serviceName] == null) {
196
+ this.servicesByServiceName[params.serviceName] = [service]
197
+ } else {
198
+ this.servicesByServiceName[params.serviceName].push(service)
196
199
  }
197
- if (
198
- resource.body.config != null &&
199
- resource.body.config.battery !== undefined
200
- ) {
201
- if (this.serviceByServiceName.Battery == null) {
202
- this.serviceByServiceName.Battery = new DeconzService.Battery(this, resource)
200
+ if (resource.body.config?.battery !== undefined) {
201
+ if (this.servicesByServiceName.Battery?.[0] == null) {
202
+ this.servicesByServiceName.Battery = [new DeconzService.Battery(this, resource)]
203
203
  }
204
- service.batteryService = this.serviceByServiceName.Battery
204
+ service.batteryService = this.servicesByServiceName.Battery[0]
205
205
  }
206
206
  return service
207
207
  }
@@ -237,12 +237,8 @@ class DeconzAccessory extends AccessoryDelegate {
237
237
  multiClip: undefined,
238
238
  multiLight: undefined,
239
239
  logLevel: this.values.logLevel,
240
- lowBatteryThreshold: this.serviceByServiceName.Battery == null
241
- ? undefined
242
- : this.serviceByServiceName.Battery.values.lowBatteryThreshold,
243
- // offset: this.serviceByServiceName.Temperature == null
244
- // ? undefined
245
- // : this.serviceByServiceName.Temperature.values.offset,
240
+ lowBatteryThreshold: this.servicesByServiceName?.Battery?.[0].values.lowBatteryThreshold,
241
+ // offset: this.servicesByServiceName?.Temperature?.[0].values.offset,
246
242
  serviceName: this.values.serviceName,
247
243
  splitLight: undefined,
248
244
  venetianBlind: this.service.values.venetianBlind,
@@ -290,21 +286,13 @@ class DeconzAccessory extends AccessoryDelegate {
290
286
  responseBody[key] = value
291
287
  continue
292
288
  case 'lowBatteryThreshold':
293
- if (this.serviceByServiceName.Battery != null) {
289
+ if (this.servicesByServiceName.Battery?.[0] != null) {
294
290
  value = OptionParser.toInt(key, body[key], 10, 100)
295
- this.serviceByServiceName.Battery.values[key] = value
291
+ this.servicesByServiceName.Battery[0].values[key] = value
296
292
  responseBody[key] = value
297
293
  continue
298
294
  }
299
295
  break
300
- // case 'offset': // TODO: doesn't work because of fromHomeKit
301
- // if (this.serviceByServiceName.Temperature != null) {
302
- // const value = OptionParser.toNumber(key, body[key], -5, 5)
303
- // this.serviceByServiceName.Temperature.values[key] = value
304
- // responseBody[key] = value
305
- // continue
306
- // }
307
- // break
308
296
  case 'serviceName':
309
297
  if (this.values.serviceName != null) {
310
298
  value = OptionParser.toString(key, body[key])
@@ -30,6 +30,9 @@ class Humidity extends DeconzService.SensorsResource {
30
30
  if (state.humidity != null) {
31
31
  this.values.humidity = Math.round(state.humidity / 10) / 10
32
32
  }
33
+ if (state.moisture != null) {
34
+ this.values.humidity = Math.round(state.moisture / 10) / 10
35
+ }
33
36
  super.updateState(state)
34
37
  }
35
38
  }
@@ -0,0 +1,103 @@
1
+ // homebridge-deconz/lib/DeconzService/Label.js
2
+ // Copyright© 2022-2023 Erik Baauw. All rights reserved.
3
+ //
4
+ // Homebridge plugin for deCONZ.
5
+
6
+ 'use strict'
7
+
8
+ const DeconzService = require('.')
9
+
10
+ /**
11
+ * @memberof DeconzService
12
+ */
13
+ class Label extends DeconzService.SensorsResource {
14
+ constructor (accessory, resource, params = {}) {
15
+ params.Service = accessory.Services.hap.ServiceLabel
16
+ super(accessory, resource, params)
17
+
18
+ this.addCharacteristicDelegate({
19
+ key: 'namespace',
20
+ Characteristic: this.Characteristics.hap.ServiceLabelNamespace,
21
+ value: resource.capabilities.namespace
22
+ })
23
+
24
+ this.addCharacteristicDelegates({ noLastUpdated: true })
25
+
26
+ this.buttonResources = {}
27
+ this.buttonServices = {}
28
+ this.hasRepeat = false
29
+ }
30
+
31
+ createButtonServices (resource) {
32
+ if (resource.body.type.endsWith('Switch')) {
33
+ this.buttonResources[resource.rpath] = {
34
+ buttonEvent: resource.body.state.buttonevent,
35
+ lastUpdated: '',
36
+ toButtonEvent: resource.capabilities.toButtonEvent
37
+ }
38
+ } else if (resource.body.type.endsWith('RelativeRotary')) {
39
+ const keys = Object.keys(resource.capabilities.buttons)
40
+ this.buttonResources[resource.rpath] = {
41
+ lastUpdated: '',
42
+ right: keys[0],
43
+ left: keys[1]
44
+ }
45
+ }
46
+ for (const i in resource.capabilities.buttons) {
47
+ const { label, events, hasRepeat } = resource.capabilities.buttons[i]
48
+ if (hasRepeat) {
49
+ this.hasRepeat = hasRepeat
50
+ }
51
+ this.buttonServices[i] = new DeconzService.Button(this.accessoryDelegate, {
52
+ name: this.name + ' ' + label,
53
+ button: Number(i),
54
+ events,
55
+ hasRepeat
56
+ })
57
+ }
58
+ }
59
+
60
+ updateState (state, rpath) {
61
+ const buttonResource = this.buttonResources[rpath]
62
+ if (buttonResource.lastUpdated === '') {
63
+ buttonResource.lastUpdated = state.lastupdated
64
+ } else if (
65
+ state.lastupdated != null &&
66
+ state.lastupdated !== buttonResource.lastUpdated
67
+ ) {
68
+ buttonResource.lastUpdated = state.lastupdated
69
+ if (buttonResource.buttonEvent !== undefined) {
70
+ const oldValue = buttonResource.buttonEvent
71
+ if (state.buttonevent != null) {
72
+ buttonResource.buttonEvent = buttonResource.toButtonEvent == null
73
+ ? state.buttonevent
74
+ : buttonResource.toButtonEvent(state.buttonevent)
75
+ }
76
+ // TODO handle repeat
77
+ const i = Math.floor(buttonResource.buttonEvent / 1000)
78
+ this.buttonServices[i]?.update(
79
+ buttonResource.buttonEvent, oldValue,
80
+ false // this.accessoryDelegate.settingsService.values.repeat
81
+ )
82
+ } else {
83
+ const i = state.expectedrotation >= 0
84
+ ? buttonResource.right
85
+ : buttonResource.left
86
+ this.buttonServices[i].updateRotation()
87
+ }
88
+ }
89
+ super.updateState(state)
90
+ }
91
+
92
+ updateConfig (config) {
93
+ // TODO handle change in devicemode
94
+ super.updateConfig(config)
95
+ }
96
+
97
+ async identify () {
98
+ this.debug('hasRepeat: %j', this.hasRepeat)
99
+ return super.identify()
100
+ }
101
+ }
102
+
103
+ module.exports = Label
@@ -110,7 +110,9 @@ class Light extends DeconzService.LightsResource {
110
110
  this.resource.body?.capabilities?.color?.ct?.min == null ||
111
111
  this.resource.body?.capabilities?.color?.ct?.max == null
112
112
  ) {
113
- this.warn('using default ct range')
113
+ if (this.capabilities.on) {
114
+ this.warn('using default ct range')
115
+ }
114
116
  }
115
117
  const ctMin = this.resource.body?.capabilities?.color?.ct?.min ?? 153
116
118
  const ctMax = this.resource.body?.capabilities?.color?.ct?.max ?? 500
@@ -144,7 +146,9 @@ class Light extends DeconzService.LightsResource {
144
146
  this.resource.body?.capabilities?.color?.xy?.green == null ||
145
147
  this.resource.body?.capabilities?.color?.xy?.red == null
146
148
  ) {
147
- this.warn('using default xy gamut')
149
+ if (this.capabilities.on) {
150
+ this.warn('using default xy gamut')
151
+ }
148
152
  this.capabilities.gamut = defaultGamut
149
153
  } else {
150
154
  this.capabilities.gamut = {
@@ -24,7 +24,7 @@ class Outlet extends DeconzService.LightsResource {
24
24
  }
25
25
  })
26
26
 
27
- if (!this.capabilities.on) {
27
+ if (this.resource.body.state.on === undefined) {
28
28
  this.addCharacteristicDelegate({
29
29
  key: 'anyOn',
30
30
  Characteristic: this.Characteristics.my.AnyOn,
@@ -11,6 +11,30 @@ const DeconzService = require('../DeconzService')
11
11
  * @memberof DeconzService
12
12
  */
13
13
  class Status extends DeconzService.SensorsResource {
14
+ props (caps) {
15
+ if (caps.min == null || caps.max == null) {
16
+ return undefined
17
+ }
18
+ // Eve 3.1 displays the following controls, depending on the properties:
19
+ // 1. {minValue: 0, maxValue: 1, minStep: 1} switch
20
+ // 2. {minValue: a, maxValue: b, minStep: 1}, 1 < b - a <= 20 down|up
21
+ // 3. {minValue: a, maxValue: b}, (a, b) != (0, 1) slider
22
+ // 4. {minValue: a, maxValue: b, minStep: 1}, b - a > 20 slider
23
+ // Avoid the following bugs:
24
+ // 5. {minValue: 0, maxValue: 1} nothing
25
+ // 6. {minValue: a, maxValue: b, minStep: 1}, b - a = 1 switch*
26
+ // *) switch sends values 0 and 1 instead of a and b;
27
+ if (caps.min === 0 && caps.max === 1) {
28
+ // Workaround Eve bug (case 5 above).
29
+ return { minValue: caps.min, maxValue: caps.max, minStep: 1 }
30
+ }
31
+ if (caps.max - caps.min === 1) {
32
+ // Workaround Eve bug (case 6 above).
33
+ return { minValue: caps.min, maxValue: caps.max }
34
+ }
35
+ return { minValue: caps.min, maxValue: caps.max, minStep: 1 }
36
+ }
37
+
14
38
  constructor (accessory, resource, params = {}) {
15
39
  params.Service = accessory.Services.my.Status
16
40
  super(accessory, resource, params)
@@ -29,7 +53,7 @@ class Status extends DeconzService.SensorsResource {
29
53
  this.addCharacteristicDelegate({
30
54
  key: 'status',
31
55
  Characteristic: this.Characteristics.my.Status,
32
- props: resource.capabilities.props
56
+ props: this.props(resource.capabilities)
33
57
  }).on('didSet', async (value, fromHomeKit) => {
34
58
  if (fromHomeKit) {
35
59
  await this.put('/state', { status: value })
@@ -5,101 +5,66 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const DeconzService = require('../DeconzService')
8
+ const DeconzService = require('.')
9
9
 
10
- /**
11
- * @memberof DeconzService
12
- */
13
- class Switch extends DeconzService.SensorsResource {
10
+ class Switch extends DeconzService.LightsResource {
14
11
  constructor (accessory, resource, params = {}) {
15
- params.Service = accessory.Services.hap.ServiceLabel
12
+ params.Service = accessory.Services.hap.Switch
16
13
  super(accessory, resource, params)
17
14
 
18
15
  this.addCharacteristicDelegate({
19
- key: 'namespace',
20
- Characteristic: this.Characteristics.hap.ServiceLabelNamespace,
21
- value: resource.capabilities.namespace
16
+ key: 'on',
17
+ Characteristic: this.Characteristics.hap.On,
18
+ value: this.capabilities.on
19
+ ? this.resource.body.state.on
20
+ : this.resource.body.state.all_on
21
+ }).on('didSet', (value, fromHomeKit) => {
22
+ if (fromHomeKit) {
23
+ this.put({ on: value })
24
+ }
22
25
  })
23
26
 
24
- this.addCharacteristicDelegates({ noLastUpdated: true })
27
+ if (this.resource.body.state.on === undefined) {
28
+ this.addCharacteristicDelegate({
29
+ key: 'anyOn',
30
+ Characteristic: this.Characteristics.my.AnyOn,
31
+ value: this.resource.body.state.any_on
32
+ }).on('didSet', (value, fromHomeKit) => {
33
+ if (fromHomeKit) {
34
+ this.put({ on: value })
35
+ }
36
+ })
37
+ }
25
38
 
26
- this.buttonResources = {}
27
- this.buttonServices = {}
28
- this.hasRepeat = false
29
- }
39
+ this.addCharacteristicDelegates()
30
40
 
31
- createButtonServices (resource) {
32
- if (resource.body.type.endsWith('Switch')) {
33
- this.buttonResources[resource.rpath] = {
34
- buttonEvent: resource.body.state.buttonevent,
35
- lastUpdated: '',
36
- toButtonEvent: resource.capabilities.toButtonEvent
37
- }
38
- } else if (resource.body.type.endsWith('RelativeRotary')) {
39
- const keys = Object.keys(resource.capabilities.buttons)
40
- this.buttonResources[resource.rpath] = {
41
- lastUpdated: '',
42
- right: keys[0],
43
- left: keys[1]
44
- }
45
- }
46
- for (const i in resource.capabilities.buttons) {
47
- const { label, events, hasRepeat } = resource.capabilities.buttons[i]
48
- if (hasRepeat) {
49
- this.hasRepeat = hasRepeat
50
- }
51
- this.buttonServices[i] = new DeconzService.Button(this.accessoryDelegate, {
52
- name: this.name + ' ' + label,
53
- button: Number(i),
54
- events,
55
- hasRepeat
56
- })
41
+ this.settings = {
42
+ resetTimeout: this.platform.config.resetTimeout,
43
+ waitTimeUpdate: this.platform.config.waitTimeUpdate,
44
+ wallSwitch: false
57
45
  }
58
46
  }
59
47
 
60
48
  updateState (state, rpath) {
61
- const buttonResource = this.buttonResources[rpath]
62
- if (buttonResource.lastUpdated === '') {
63
- buttonResource.lastUpdated = state.lastupdated
64
- } else if (
65
- state.lastupdated != null &&
66
- state.lastupdated !== buttonResource.lastUpdated
67
- ) {
68
- buttonResource.lastUpdated = state.lastupdated
69
- if (buttonResource.buttonEvent !== undefined) {
70
- const oldValue = buttonResource.buttonEvent
71
- if (state.buttonevent != null) {
72
- buttonResource.buttonEvent = buttonResource.toButtonEvent == null
73
- ? state.buttonevent
74
- : buttonResource.toButtonEvent(state.buttonevent)
75
- }
76
- // TODO handle repeat
77
- const i = Math.floor(buttonResource.buttonEvent / 1000)
78
- if (this.buttonServices[i] != null) {
79
- this.buttonServices[i].update(
80
- buttonResource.buttonEvent, oldValue,
81
- false // this.accessoryDelegate.settingsService.values.repeat
82
- )
83
- }
84
- } else {
85
- const i = state.expectedrotation >= 0
86
- ? buttonResource.right
87
- : buttonResource.left
88
- this.buttonServices[i].updateRotation()
49
+ for (const key in state) {
50
+ const value = state[key]
51
+ this.resource.body.state[key] = value
52
+ switch (key) {
53
+ case 'all_on':
54
+ this.values.on = value
55
+ break
56
+ case 'any_on':
57
+ this.values.anyOn = value
58
+ break
59
+ case 'on':
60
+ this.values.on = value
61
+ break
62
+ default:
63
+ break
89
64
  }
90
65
  }
91
66
  super.updateState(state)
92
67
  }
93
-
94
- updateConfig (config) {
95
- // TODO handle change in devicemode
96
- super.updateConfig(config)
97
- }
98
-
99
- async identify () {
100
- this.debug('hasRepeat: %j', this.hasRepeat)
101
- return super.identify()
102
- }
103
68
  }
104
69
 
105
70
  module.exports = Switch
@@ -27,6 +27,7 @@ class DeconzService extends ServiceDelegate {
27
27
  static get Flag () { return require('./Flag') }
28
28
  static get Gateway () { return require('./Gateway') }
29
29
  static get Humidity () { return require('./Humidity') }
30
+ static get Label () { return require('./Label') }
30
31
  static get Leak () { return require('./Leak') }
31
32
  static get Light () { return require('./Light') }
32
33
  static get LightsResource () { return require('./LightsResource') }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "displayName": "Homebridge deCONZ",
5
5
  "author": "Erik Baauw",
6
6
  "license": "Apache-2.0",
7
- "version": "0.1.10",
7
+ "version": "0.1.11",
8
8
  "keywords": [
9
9
  "homebridge-plugin",
10
10
  "homekit",
@@ -21,13 +21,13 @@
21
21
  "ui": "cli/ui.js"
22
22
  },
23
23
  "engines": {
24
- "deCONZ": "2.20.1",
24
+ "deCONZ": "2.21.2",
25
25
  "homebridge": "^1.6.0",
26
26
  "node": "^18.15.0"
27
27
  },
28
28
  "dependencies": {
29
- "homebridge-lib": "~6.3.12",
30
- "ws": "^8.12.1",
29
+ "homebridge-lib": "~6.3.13",
30
+ "ws": "^8.13.0",
31
31
  "xml2js": "~0.4.23"
32
32
  },
33
33
  "scripts": {