homebridge-deconz 0.0.10 → 0.0.13

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 (53) hide show
  1. package/README.md +3 -6
  2. package/cli/deconz.js +12 -0
  3. package/config.schema.json +1 -0
  4. package/homebridge-ui/public/index.html +26 -0
  5. package/homebridge-ui/public/style.css +0 -0
  6. package/homebridge-ui/server.js +43 -0
  7. package/lib/Deconz/ApiClient.js +34 -1
  8. package/lib/Deconz/ApiResponse.js +1 -1
  9. package/lib/Deconz/Device.js +0 -7
  10. package/lib/Deconz/Resource.js +751 -25
  11. package/lib/DeconzAccessory/Contact.js +54 -0
  12. package/lib/DeconzAccessory/Gateway.js +102 -66
  13. package/lib/DeconzAccessory/Light.js +42 -37
  14. package/lib/DeconzAccessory/Motion.js +51 -0
  15. package/lib/DeconzAccessory/Sensor.js +35 -0
  16. package/lib/DeconzAccessory/Temperature.js +63 -0
  17. package/lib/DeconzAccessory/Thermostat.js +50 -0
  18. package/lib/DeconzAccessory/WarningDevice.js +56 -0
  19. package/lib/DeconzAccessory/WindowCovering.js +47 -0
  20. package/lib/DeconzAccessory/index.js +144 -2
  21. package/lib/DeconzPlatform.js +5 -2
  22. package/lib/DeconzService/AirPressure.js +43 -0
  23. package/lib/DeconzService/AirQuality.js +20 -11
  24. package/lib/DeconzService/Alarm.js +13 -9
  25. package/lib/DeconzService/Battery.js +53 -0
  26. package/lib/DeconzService/Button.js +9 -2
  27. package/lib/DeconzService/CarbonMonoxide.js +38 -0
  28. package/lib/DeconzService/Consumption.js +65 -0
  29. package/lib/DeconzService/Contact.js +60 -0
  30. package/lib/DeconzService/Daylight.js +132 -0
  31. package/lib/DeconzService/DeviceSettings.js +11 -0
  32. package/lib/DeconzService/Flag.js +52 -0
  33. package/lib/DeconzService/Humidity.js +37 -0
  34. package/lib/DeconzService/Leak.js +38 -0
  35. package/lib/DeconzService/Light.js +122 -172
  36. package/lib/DeconzService/LightLevel.js +54 -0
  37. package/lib/DeconzService/LightsResource.js +112 -0
  38. package/lib/DeconzService/Motion.js +101 -0
  39. package/lib/DeconzService/Outlet.js +76 -0
  40. package/lib/DeconzService/Power.js +83 -0
  41. package/lib/DeconzService/SensorsResource.js +96 -0
  42. package/lib/DeconzService/Smoke.js +38 -0
  43. package/lib/DeconzService/Status.js +53 -0
  44. package/lib/DeconzService/Switch.js +93 -0
  45. package/lib/DeconzService/Temperature.js +63 -0
  46. package/lib/DeconzService/Thermostat.js +175 -0
  47. package/lib/DeconzService/WarningDevice.js +68 -0
  48. package/lib/DeconzService/WindowCovering.js +162 -0
  49. package/lib/DeconzService/index.js +79 -25
  50. package/package.json +6 -5
  51. package/lib/Client/ApiError.js +0 -42
  52. package/lib/DeconzAccessory/Device.js +0 -69
  53. package/lib/DeconzService/Sensor.js +0 -61
@@ -6,24 +6,21 @@
6
6
  'use strict'
7
7
 
8
8
  const homebridgeLib = require('homebridge-lib')
9
+ const Deconz = require('../Deconz')
10
+ const DeconzAccessory = require('../DeconzAccessory')
11
+ const DeconzService = require('../DeconzService')
9
12
 
10
13
  const { toInstance, toInt, toObject, toString } = homebridgeLib.OptionParser
11
14
  const { defaultGamut } = homebridgeLib.Colour
12
-
13
- const DeconzAccessory = require('../DeconzAccessory')
14
-
15
+ const { buttonEvent } = Deconz.ApiClient
16
+ const { SINGLE, DOUBLE, LONG } = DeconzService.Button
15
17
  const rtypes = ['lights', 'sensors', 'groups']
16
18
 
17
19
  // From low to high.
18
20
  const sensorsPrios = [
19
21
  'Power',
20
22
  'Consumption',
21
- 'Switch',
22
- 'AirQuality',
23
- 'Pressure',
24
- 'Humidiy',
25
23
  'Temperature',
26
- 'LightLevel',
27
24
  'Presence',
28
25
  'OpenClose',
29
26
  'Thermostat'
@@ -76,6 +73,17 @@ const hueGamutTypeByModel = {
76
73
  LST002: 'C' // Hue LightStrips Plus
77
74
  }
78
75
 
76
+ const hueTapMap = {
77
+ 34: 1002, // press 1
78
+ 16: 2002, // press 2
79
+ 17: 3002, // press 3
80
+ 18: 4002, // press 4
81
+ 100: 5002, // press 1 and 2
82
+ 101: 0, // release 1 and 2
83
+ 98: 6002, // press 3 and 4
84
+ 99: 0 // release 3 and 4
85
+ }
86
+
79
87
  /** Delegate class for a resource on a deCONZ gateway.
80
88
  *
81
89
  * @memberof Deconz
@@ -153,6 +161,11 @@ class Resource {
153
161
  */
154
162
  this.subtype = endpoint + (cluster == null ? '' : '-' + cluster)
155
163
 
164
+ /** Zigbee endpoint.
165
+ * @type {string}
166
+ */
167
+ this.endpoint = endpoint
168
+
156
169
  /** Zigbee device vs virtual device.
157
170
  *
158
171
  * Derived from the resource type and, for `sensors`, on the `type` in the
@@ -242,7 +255,7 @@ class Resource {
242
255
  */
243
256
  get prio () {
244
257
  if (this.rtype === 'groups') return -1
245
- if (this.rtype === 'lights') return this.subtype
258
+ if (this.rtype === 'lights') return this.endpoint
246
259
  return sensorsPrios.indexOf(this.type)
247
260
  }
248
261
 
@@ -306,7 +319,7 @@ class Resource {
306
319
  // case 'CLIPDoorLock': return null
307
320
  case 'Daylight': return 'Daylight'
308
321
  case 'ZHAFire':
309
- case 'CLIPFire': return 'Fire'
322
+ case 'CLIPFire': return 'Smoke'
310
323
  case 'CLIPGenericFlag': return 'Flag'
311
324
  case 'CLIPGenericStatus': return 'Status'
312
325
  case 'ZHAHumidity':
@@ -316,17 +329,17 @@ class Resource {
316
329
  // case 'ZHAMoisture':
317
330
  // case 'CLIPMoisture': return null
318
331
  case 'ZHAOpenClose':
319
- case 'CLIPOpenClose': return 'OpenClose'
332
+ case 'CLIPOpenClose': return 'Contact'
320
333
  case 'ZHAPower':
321
334
  case 'CLIPPower': return 'Power'
322
335
  case 'ZHAPresence':
323
- case 'CLIPPresence': return 'Presence'
336
+ case 'CLIPPresence': return 'Motion'
324
337
  case 'ZHAPressure':
325
- case 'CLIPPressure': return 'Pressure'
338
+ case 'CLIPPressure': return 'AirPressure'
326
339
  case 'ZHASpectral': return ''
327
340
  case 'ZGPSwitch':
328
- case 'ZHASwitch': return 'Switch'
329
- case 'CLIPSwitch': return ''
341
+ case 'ZHASwitch':
342
+ case 'CLIPSwitch': return 'Switch'
330
343
  case 'ZHATemperature':
331
344
  case 'CLIPTemperature': return 'Temperature'
332
345
  case 'ZHAThermostat':
@@ -334,9 +347,9 @@ class Resource {
334
347
  case 'ZHATime':
335
348
  case 'CLIPTime': return ''
336
349
  case 'ZHAVibration':
337
- case 'CLIPVibration': return 'Presence'
350
+ case 'CLIPVibration': return 'Motion'
338
351
  case 'ZHAWater':
339
- case 'CLIPWater': return 'Water'
352
+ case 'CLIPWater': return 'Leak'
340
353
  default: return null
341
354
  }
342
355
  }
@@ -353,7 +366,7 @@ class Resource {
353
366
  this.model === 'RM01' && // 6715 U-500 with 6736-84.
354
367
  this.capabilities.bri && this.body.type === 'On/Off light' // Issue #241
355
368
  ) {
356
- gateway.debug(
369
+ gateway.vdebug(
357
370
  '%s: ignoring state.bri for %s', this.rpath, this.body.type
358
371
  )
359
372
  this.capabilities.bri = false
@@ -377,32 +390,32 @@ class Resource {
377
390
  }
378
391
  if (this.model === 'GLEDOPTO') { // Issue #244
379
392
  if (
380
- this.subtype === '0a' &&
393
+ this.endpoint === '0A' &&
381
394
  this.body.type === 'Dimmable light' &&
382
395
  this.firmware === '1.0.2'
383
396
  ) {
384
397
  this.model = 'RGBW'
385
398
  } else if (
386
- this.subtype === '0b' &&
399
+ this.endpoint === '0B' &&
387
400
  this.body.type === 'Color temperature light' &&
388
401
  this.firmware === '1.3.002'
389
402
  ) {
390
403
  this.model = 'WW/CW'
391
404
  } else if (
392
- this.subtype === '0b' &&
405
+ this.endpoint === '0B' &&
393
406
  this.body.type === 'Extended color light' &&
394
407
  this.firmware === '1.0.2'
395
408
  ) {
396
409
  this.model = 'RGB+CCT'
397
- const device = gateway.resourceById[this.id]
398
- if (device != null && device.resourceBySubtype.length > 1) {
410
+ const device = gateway.deviceById[this.id]
411
+ if (device != null) {
399
412
  this.model = 'RGBW'
400
413
  this.capabilities.ct = false
401
414
  }
402
415
  } else {
403
416
  return
404
417
  }
405
- gateway.debug('%s: set model to %j', this.rpath, this.model)
418
+ gateway.vdebug('%s: set model to %j', this.rpath, this.model)
406
419
  }
407
420
  break
408
421
  case 'IKEA of Sweden':
@@ -449,7 +462,7 @@ class Resource {
449
462
  g: [0.11, 0.82],
450
463
  b: [0.13, 0.04]
451
464
  }
452
- if (this.capabilities.colorloop) {
465
+ if (this.capabilities.colorLoop) {
453
466
  this.capabilities.effects = [
454
467
  'Sunset', 'Party', 'Worklight', 'Campfire', 'Romance', 'Nightlight'
455
468
  ]
@@ -476,6 +489,719 @@ class Resource {
476
489
  break
477
490
  }
478
491
  }
492
+
493
+ /** Patch a resource corresponding to a `Flag` service.
494
+ * @param {DeconzAccessory.Gateway} gateway - The gateway.
495
+ */
496
+ patchFlag (gateway) {
497
+ if (
498
+ this.body.manufacturername === 'homebridge-hue' &&
499
+ this.body.modelid === 'CLIPGenericFlag' &&
500
+ this.body.swversion === '0'
501
+ ) {
502
+ this.capabilities.readonly = true
503
+ }
504
+ }
505
+
506
+ /** Patch a resource corresponding to a `Flag` service.
507
+ * @param {DeconzAccessory.Gateway} gateway - The gateway.
508
+ */
509
+ patchStatus (gateway) {
510
+ if (
511
+ this.body.manufacturername === 'homebridge-hue' &&
512
+ this.body.modelid === 'CLIPGenericStatus'
513
+ ) {
514
+ const a = this.body.swversion.split(',')
515
+ const min = parseInt(a[0])
516
+ const max = parseInt(a[1])
517
+ const step = parseInt(a[2])
518
+ // Eve 3.1 displays the following controls, depending on the properties:
519
+ // 1. {minValue: 0, maxValue: 1, minStep: 1} switch
520
+ // 2. {minValue: a, maxValue: b, minStep: 1}, 1 < b - a <= 20 down|up
521
+ // 3. {minValue: a, maxValue: b}, (a, b) != (0, 1) slider
522
+ // 4. {minValue: a, maxValue: b, minStep: 1}, b - a > 20 slider
523
+ // Avoid the following bugs:
524
+ // 5. {minValue: 0, maxValue: 1} nothing
525
+ // 6. {minValue: a, maxValue: b, minStep: 1}, b - a = 1 switch*
526
+ // *) switch sends values 0 and 1 instead of a and b;
527
+ if (min === 0 && max === 0) {
528
+ this.capabilities.readonly = true
529
+ } else if (min >= -127 && max <= 127 && min < max) {
530
+ if (min === 0 && max === 1) {
531
+ // Workaround Eve bug (case 5 above).
532
+ this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
533
+ } else if (max - min === 1) {
534
+ // Workaround Eve bug (case 6 above).
535
+ this.capabilities.props = { minValue: min, maxValue: max }
536
+ } else if (step !== 1) {
537
+ // Default to slider for backwards compatibility.
538
+ this.capabilities.props = { minValue: min, maxValue: max }
539
+ } else {
540
+ this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
541
+ }
542
+ }
543
+ }
544
+ }
545
+
546
+ /** Patch a resource corresponding to a `Switch` service.
547
+ * @param {DeconzAccessory.Gateway} gateway - The gateway.
548
+ */
549
+ patchSwitch (gateway) {
550
+ const buttons = []
551
+ let dots = false
552
+ switch (this.manufacturer) {
553
+ case 'Bitron Home':
554
+ switch (this.model) {
555
+ case '902010/23': // Bitron remote, see #639.
556
+ dots = true
557
+ buttons.push([1, 'Dim Up', SINGLE])
558
+ buttons.push([2, 'On', SINGLE])
559
+ buttons.push([3, 'Off', SINGLE])
560
+ buttons.push([4, 'Dim Down', SINGLE])
561
+ break
562
+ default:
563
+ break
564
+ }
565
+ break
566
+ case 'Busch-Jaeger':
567
+ switch (this.model) {
568
+ case 'RM01': // Busch-Jaeger Light Link control element (mains-powered)
569
+ case 'RB01': // Busch-Jaeger Light Link wall-mounted transmitter
570
+ if (this.endpoint === '0A') {
571
+ buttons.push([1, 'Button 1', SINGLE | LONG])
572
+ buttons.push([2, 'Button 2', SINGLE | LONG])
573
+ } else if (this.endpoint === '0B') {
574
+ buttons.push([3, 'Button 3', SINGLE | LONG])
575
+ buttons.push([4, 'Button 4', SINGLE | LONG])
576
+ } else if (this.endpoint === '0C') {
577
+ buttons.push([5, 'Button 5', SINGLE | LONG])
578
+ buttons.push([6, 'Button 6', SINGLE | LONG])
579
+ } else if (this.endpoint === '0D') {
580
+ buttons.push([7, 'Button 7', SINGLE | LONG])
581
+ buttons.push([8, 'Button 8', SINGLE | LONG])
582
+ }
583
+ break
584
+ default:
585
+ break
586
+ }
587
+ break
588
+ case 'Echostar':
589
+ switch (this.model) {
590
+ case 'Bell':
591
+ buttons.push([1, 'Front Doorbell', SINGLE])
592
+ buttons.push([2, 'Rear Doorbell', SINGLE])
593
+ break
594
+ default:
595
+ break
596
+ }
597
+ break
598
+ case 'ELKO':
599
+ switch (this.model) {
600
+ case 'ElkoDimmerRemoteZHA': // ELKO ESH 316 Endevender RF, see #922.
601
+ buttons.push([1, 'Press', SINGLE])
602
+ buttons.push([2, 'Dim Up', SINGLE])
603
+ buttons.push([3, 'Dim Down', SINGLE])
604
+ break
605
+ default:
606
+ break
607
+ }
608
+ break
609
+ case 'Heiman':
610
+ switch (this.model) {
611
+ case 'RC-EF-3.0':
612
+ dots = true
613
+ buttons.push([1, 'HomeMode', SINGLE])
614
+ buttons.push([2, 'Disarm', SINGLE])
615
+ buttons.push([3, 'SOS', SINGLE])
616
+ buttons.push([4, 'Arm', SINGLE])
617
+ break
618
+ default:
619
+ break
620
+ }
621
+ break
622
+ case 'IKEA of Sweden':
623
+ switch (this.model) {
624
+ case 'Remote Control N2':
625
+ buttons.push([1, 'Dim Up', SINGLE | LONG])
626
+ buttons.push([2, 'Dim Down', SINGLE | LONG])
627
+ buttons.push([3, 'Previous', SINGLE | LONG])
628
+ buttons.push([4, 'Next', SINGLE | LONG])
629
+ break
630
+ case 'SYMFONISK Sound Controller':
631
+ buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
632
+ if (this.body.mode === 1) {
633
+ buttons.push([2, 'Turn Right', LONG])
634
+ buttons.push([3, 'Turn Left', LONG])
635
+ } else {
636
+ buttons.push([2, 'Turn Right', SINGLE])
637
+ buttons.push([3, 'Turn Left', SINGLE])
638
+ }
639
+ break
640
+ case 'TRADFRI SHORTCUT Button':
641
+ buttons.push([1, 'Button', SINGLE | LONG])
642
+ break
643
+ case 'TRADFRI on/off switch':
644
+ buttons.push([1, 'On', SINGLE | LONG])
645
+ buttons.push([2, 'Off', SINGLE | LONG])
646
+ break
647
+ case 'TRADFRI open/close remote':
648
+ buttons.push([1, 'Open', SINGLE | LONG])
649
+ buttons.push([2, 'Close', SINGLE | LONG])
650
+ break
651
+ case 'TRADFRI remote control':
652
+ buttons.push([1, 'On/Off', SINGLE])
653
+ buttons.push([2, 'Dim Up', SINGLE | LONG])
654
+ buttons.push([3, 'Dim Down', SINGLE | LONG])
655
+ buttons.push([4, 'Previous', SINGLE | LONG])
656
+ buttons.push([5, 'Next', SINGLE | LONG])
657
+ break
658
+ case 'TRADFRI wireless dimmer':
659
+ if (this.obj.mode === 1) {
660
+ buttons.push([1, 'Turn Right', SINGLE | LONG])
661
+ buttons.push([2, 'Turn Left', SINGLE | LONG])
662
+ } else {
663
+ buttons.push([1, 'On', SINGLE])
664
+ buttons.push([2, 'Dim Up', SINGLE])
665
+ buttons.push([3, 'Dim Down', SINGLE])
666
+ buttons.push([4, 'Off', SINGLE])
667
+ }
668
+ break
669
+ default:
670
+ break
671
+ }
672
+ break
673
+ case 'Insta':
674
+ switch (this.model) {
675
+ case 'HS_4f_GJ_1': // Gira/Jung Light Link hand transmitter
676
+ case 'WS_3f_G_1': // Gira Light Link wall transmitter
677
+ case 'WS_4f_J_1': // Jung Light Link wall transmitter
678
+ buttons.push([1, 'Off', SINGLE | DOUBLE | LONG])
679
+ buttons.push([2, 'On', SINGLE | DOUBLE | LONG])
680
+ buttons.push([3, 'Scene 1', SINGLE])
681
+ buttons.push([4, 'Scene 2', SINGLE])
682
+ buttons.push([5, 'Scene 3', SINGLE])
683
+ buttons.push([6, 'Scene 4', SINGLE])
684
+ if (this.model !== 'WS_3f_G_1') {
685
+ buttons.push([7, 'Scene 5', SINGLE])
686
+ buttons.push([8, 'Scene 6', SINGLE])
687
+ }
688
+ break
689
+ default:
690
+ break
691
+ }
692
+ break
693
+ case 'LDS':
694
+ switch (this.model) {
695
+ case 'ZBT-DIMController-D0800':
696
+ buttons.push([1, 'On/Off', SINGLE])
697
+ buttons.push([2, 'Dim Up', SINGLE | LONG])
698
+ buttons.push([3, 'Dim Down', SINGLE | LONG])
699
+ buttons.push([4, 'Scene', SINGLE | LONG])
700
+ break
701
+ default:
702
+ break
703
+ }
704
+ break
705
+ case 'LIDL Livarno Lux':
706
+ switch (this.model) {
707
+ case 'HG06323':
708
+ buttons.push([1, 'On', SINGLE | DOUBLE | LONG])
709
+ buttons.push([2, 'Dim Up', SINGLE | LONG])
710
+ buttons.push([3, 'Dim Down', SINGLE | LONG])
711
+ buttons.push([4, 'Off', SINGLE])
712
+ break
713
+ default:
714
+ break
715
+ }
716
+ break
717
+ case 'LUMI':
718
+ switch (this.model) {
719
+ case 'lumi.ctrl_neutral1':
720
+ buttons.push([1, 'Button', SINGLE])
721
+ break
722
+ case 'lumi.ctrl_neutral2':
723
+ buttons.push([1, 'Left', SINGLE])
724
+ buttons.push([2, 'Right', SINGLE])
725
+ buttons.push([3, 'Both', SINGLE])
726
+ break
727
+ case 'lumi.remote.b1acn01':
728
+ case 'lumi.remote.b186acn01':
729
+ case 'lumi.remote.b186acn02':
730
+ buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
731
+ break
732
+ case 'lumi.remote.b28ac1':
733
+ case 'lumi.remote.b286acn01':
734
+ case 'lumi.remote.b286acn02':
735
+ buttons.push([1, 'Left', SINGLE | DOUBLE | LONG])
736
+ buttons.push([2, 'Right', SINGLE | DOUBLE | LONG])
737
+ buttons.push([3, 'Both', SINGLE | DOUBLE | LONG])
738
+ break
739
+ case 'lumi.remote.b286opcn01': // Xiaomi Aqara Opple, see #637.
740
+ case 'lumi.remote.b486opcn01': // Xiaomi Aqara Opple, see #637.
741
+ case 'lumi.remote.b686opcn01': // Xiaomi Aqara Opple, see #637.
742
+ buttons.push([1, '1', SINGLE | DOUBLE | LONG])
743
+ buttons.push([2, '2', SINGLE | DOUBLE | LONG])
744
+ if (this.model !== 'lumi.remote.b286opcn01') {
745
+ buttons.push([3, '3', SINGLE | DOUBLE | LONG])
746
+ buttons.push([4, '4', SINGLE | DOUBLE | LONG])
747
+ if (this.model === 'lumi.remote.b686opcn01') {
748
+ buttons.push([5, '5', SINGLE | DOUBLE | LONG])
749
+ buttons.push([6, '6', SINGLE | DOUBLE | LONG])
750
+ }
751
+ }
752
+ break
753
+ case 'lumi.sensor_86sw1': // Xiaomi wall switch (single button)
754
+ case 'lumi.ctrl_ln1.aq1':
755
+ buttons.push([1, 'Button', SINGLE | DOUBLE])
756
+ break
757
+ case 'lumi.sensor_86sw2': // Xiaomi wall switch (two buttons)
758
+ case 'lumi.ctrl_ln2.aq1':
759
+ buttons.push([1, 'Left', SINGLE | DOUBLE])
760
+ buttons.push([2, 'Right', SINGLE | DOUBLE])
761
+ buttons.push([3, 'Both', SINGLE | DOUBLE])
762
+ break
763
+ case 'lumi.sensor_cube':
764
+ case 'lumi.sensor_cube.aqgl01':
765
+ if (this.endpoint === '02') {
766
+ buttons.push([1, 'Side 1', SINGLE | DOUBLE | LONG])
767
+ buttons.push([2, 'Side 2', SINGLE | DOUBLE | LONG])
768
+ buttons.push([3, 'Side 3', SINGLE | DOUBLE | LONG])
769
+ buttons.push([4, 'Side 4', SINGLE | DOUBLE | LONG])
770
+ buttons.push([5, 'Side 5', SINGLE | DOUBLE | LONG])
771
+ buttons.push([6, 'Side 6', SINGLE | DOUBLE | LONG])
772
+ buttons.push([7, 'Cube', SINGLE | DOUBLE | LONG])
773
+ this.capabilities.toButtonEvent = (v) => {
774
+ const button = Math.floor(v / 1000)
775
+ let event = v % 1000
776
+ if (v === 7000) { // Wakeup
777
+ event = buttonEvent.SHORT_RELEASE
778
+ } else if (v === 7007 || v === 7008) { // Shake, Drop
779
+ } else if (event === 0) { // Push
780
+ event = buttonEvent.LONG_RELEASE
781
+ } else if (event === button) { // Double tap
782
+ event = buttonEvent.DOUBLE_PRESS
783
+ } else { // Flip
784
+ event = buttonEvent.SHORT_RELEASE
785
+ }
786
+ return button * 1000 + event
787
+ }
788
+ } else if (this.endpoint === '03') {
789
+ buttons.push([8, 'Turn Right', SINGLE | DOUBLE | LONG])
790
+ buttons.push([9, 'Turn Left', SINGLE | DOUBLE | LONG])
791
+ this.capabilities.toButtonEvent = (v) => {
792
+ const button = v > 0 ? 8 : 9
793
+ const event = Math.abs(v) < 4500
794
+ ? buttonEvent.SHORT_RELEASE
795
+ : Math.abs(v) < 9000
796
+ ? buttonEvent.DOUBLE_PRESS
797
+ : buttonEvent.LONG_RELEASE
798
+ return button * 1000 + event
799
+ }
800
+ }
801
+ break
802
+ case 'lumi.sensor_switch': // Xiaomi Mi wireless switch
803
+ case 'lumi.sensor_switch.aq2': // Xiaomi Aqara smart wireless switch
804
+ case 'lumi.sensor_switch.aq3': // Xiaomi Aqara smart wireless switch with gyro
805
+ buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
806
+ break
807
+ default:
808
+ break
809
+ }
810
+ break
811
+ case 'Lutron':
812
+ switch (this.model) {
813
+ case 'LZL4BWHL01 Remote': // Lutron Pico, see 102.
814
+ buttons.push([1, 'On', SINGLE])
815
+ buttons.push([2, 'Dim Up', LONG])
816
+ buttons.push([3, 'Dim Down', LONG])
817
+ buttons.push([4, 'Off', SINGLE])
818
+ break
819
+ case 'Z3-1BRL': // Lutron Aurora, see #522.
820
+ buttons.push([1, 'Button', SINGLE])
821
+ buttons.push([2, 'Turn Right', SINGLE])
822
+ buttons.push([3, 'Turn Left', SINGLE])
823
+ break
824
+ default:
825
+ break
826
+ }
827
+ break
828
+ case 'MLI':
829
+ switch (this.model) {
830
+ case 'ZBT-Remote-ALL-RGBW': // Tint remote control by Müller-Licht see deconz-rest-plugin#1209
831
+ buttons.push([1, 'On/Off', SINGLE])
832
+ buttons.push([2, 'Dim Up', SINGLE | LONG])
833
+ buttons.push([3, 'Dim Down', SINGLE | LONG])
834
+ buttons.push([4, 'Warm', SINGLE])
835
+ buttons.push([5, 'Cool', SINGLE])
836
+ buttons.push([6, 'Colour Wheel', SINGLE])
837
+ buttons.push([7, 'Work Light', SINGLE])
838
+ buttons.push([8, 'Sunset', SINGLE])
839
+ buttons.push([9, 'Party', SINGLE])
840
+ buttons.push([10, 'Night Light', SINGLE])
841
+ buttons.push([11, 'Campfire', SINGLE])
842
+ buttons.push([12, 'Romance', SINGLE])
843
+ break
844
+ default:
845
+ break
846
+ }
847
+ break
848
+ case 'OSRAM':
849
+ switch (this.model) {
850
+ case 'Lightify Switch Mini':
851
+ buttons.push([1, 'Up', SINGLE | LONG])
852
+ buttons.push([2, 'Down', SINGLE | LONG])
853
+ buttons.push([3, 'Middle', SINGLE | LONG])
854
+ break
855
+ default:
856
+ break
857
+ }
858
+ break
859
+ case 'Philips':
860
+ case 'Signify Netherlands B.V.':
861
+ switch (this.model) {
862
+ case 'RDM001': // Hue wall switch module
863
+ switch (this.body.config.devicemode) {
864
+ case 'singlerocker':
865
+ buttons.push([1, 'Rocker 1', SINGLE])
866
+ break
867
+ case 'singlepushbutton':
868
+ buttons.push([1, 'Push Button 1', SINGLE | LONG, true])
869
+ break
870
+ case 'dualrocker':
871
+ buttons.push([1, 'Rocker 1', SINGLE])
872
+ buttons.push([2, 'Rocker 2', SINGLE])
873
+ break
874
+ case 'dualpushbutton':
875
+ buttons.push([1, 'Push Button 1', SINGLE | LONG, true])
876
+ buttons.push([2, 'Push Button 2', SINGLE | LONG, true])
877
+ break
878
+ default:
879
+ break
880
+ }
881
+ break
882
+ case 'ROM001': // Hue smart button
883
+ buttons.push([1, 'Button', SINGLE | LONG, true])
884
+ break
885
+ case 'RWL020':
886
+ case 'RWL021': // Hue dimmer switch
887
+ buttons.push([1, 'On', SINGLE | LONG])
888
+ buttons.push([2, 'Dim Up', SINGLE | LONG, true])
889
+ buttons.push([3, 'Dim Down', SINGLE | LONG, true])
890
+ buttons.push([4, 'Off', SINGLE | LONG])
891
+ break
892
+ case 'RWL022': // Hue dimmer switch (2021)
893
+ buttons.push([1, 'On/Off', SINGLE | LONG])
894
+ buttons.push([2, 'Dim Up', SINGLE | LONG, true])
895
+ buttons.push([3, 'Dim Down', SINGLE | LONG, true])
896
+ buttons.push([4, 'Hue', SINGLE | LONG])
897
+ break
898
+ case 'ZGPSWITCH': // Hue tap
899
+ dots = true
900
+ buttons.push([1, '1', SINGLE])
901
+ buttons.push([2, '2', SINGLE])
902
+ buttons.push([3, '3', SINGLE])
903
+ buttons.push([4, '4', SINGLE])
904
+ buttons.push([5, '1 and 2', SINGLE])
905
+ buttons.push([6, '3 and 4', SINGLE])
906
+ this.capabilities.toButtonEvent = (v) => {
907
+ return hueTapMap[v]
908
+ }
909
+ break
910
+ default:
911
+ break
912
+ }
913
+ break
914
+ case 'PhilipsFoH':
915
+ if (this.model === 'FOHSWITCH') { // Friends-of-Hue switch
916
+ buttons.push([1, 'Top Left', SINGLE | LONG])
917
+ buttons.push([2, 'Bottom Left', SINGLE | LONG])
918
+ buttons.push([3, 'Top Right', SINGLE | LONG])
919
+ buttons.push([4, 'Bottom Right', SINGLE | LONG])
920
+ buttons.push([5, 'Top Both', SINGLE | LONG])
921
+ buttons.push([6, 'Bottom Both', SINGLE | LONG])
922
+ }
923
+ break
924
+ case 'Samjin':
925
+ switch (this.model) {
926
+ case 'button':
927
+ buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
928
+ break
929
+ default:
930
+ break
931
+ }
932
+ break
933
+ case 'Sunricher':
934
+ switch (this.model) {
935
+ case 'ZG2833K8_EU05': // Sunricher 8-button remote, see #529.
936
+ if (this.endpoint === '01') {
937
+ buttons.push([1, 'On 1', SINGLE | LONG])
938
+ buttons.push([2, 'Off 1', SINGLE | LONG])
939
+ } else if (this.endpoint === '02') {
940
+ buttons.push([3, 'On 2', SINGLE | LONG])
941
+ buttons.push([4, 'Off 2', SINGLE | LONG])
942
+ } else if (this.endpoint === '03') {
943
+ buttons.push([5, 'On 3', SINGLE | LONG])
944
+ buttons.push([6, 'Off 3', SINGLE | LONG])
945
+ } else if (this.endpoint === '04') {
946
+ buttons.push([7, 'On 4', SINGLE | LONG])
947
+ buttons.push([8, 'Off 4', SINGLE | LONG])
948
+ }
949
+ break
950
+ case 'ZG2833PAC': // Sunricher C4
951
+ buttons.push([1, 'Rocker 1', SINGLE])
952
+ buttons.push([2, 'Rocker 2', SINGLE])
953
+ buttons.push([3, 'Rocker 3', SINGLE])
954
+ buttons.push([4, 'Rocker 4', SINGLE])
955
+ break
956
+ case 'ZGRC-KEY-002': // Sunricher CCT remote, see #529.
957
+ buttons.push([1, 'On', SINGLE])
958
+ buttons.push([2, 'Off', SINGLE])
959
+ buttons.push([3, 'Dim', LONG])
960
+ buttons.push([4, 'C/W', SINGLE | LONG])
961
+ break
962
+ default:
963
+ break
964
+ }
965
+ break
966
+ case '_TZ3000_arfwfgoa':
967
+ switch (this.model) {
968
+ case 'TS0042': // Tuys 2-button switch, single endpoint
969
+ buttons.push([1, 'Left', SINGLE | DOUBLE | LONG])
970
+ buttons.push([2, 'Right', SINGLE | DOUBLE | LONG])
971
+ break
972
+ default:
973
+ break
974
+ }
975
+ break
976
+ case '_TZ3000_dfgbtub0':
977
+ case '_TZ3000_i3rjdrwu':
978
+ switch (this.model) {
979
+ case 'TS0042': // Tuya 2-button switch, see #1060.
980
+ if (this.endpoint === '01') {
981
+ buttons.push([1, 'Button 1', SINGLE | DOUBLE | LONG])
982
+ } else if (this.endpoint === '02') {
983
+ buttons.push([2, 'Button 2', SINGLE | DOUBLE | LONG])
984
+ }
985
+ break
986
+ default:
987
+ break
988
+ }
989
+ break
990
+ case '_TZ3000_pzui3skt':
991
+ switch (this.model) {
992
+ case 'TS0041': // Tuya 1-button switch
993
+ buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
994
+ break
995
+ default:
996
+ break
997
+ }
998
+ break
999
+ case '_TZ3000_rrjr1q0u':
1000
+ switch (this.model) {
1001
+ case 'TS0043': // Tuya 3-button switch
1002
+ buttons.push([1, 'Left', SINGLE | DOUBLE | LONG])
1003
+ buttons.push([2, 'Middle', SINGLE | DOUBLE | LONG])
1004
+ buttons.push([3, 'Right', SINGLE | DOUBLE | LONG])
1005
+ break
1006
+ default:
1007
+ break
1008
+ }
1009
+ break
1010
+ case '_TZ3000_vp6clf9d':
1011
+ switch (this.model) {
1012
+ case 'TS0044':
1013
+ buttons.push([1, 'Bottom Left', SINGLE | DOUBLE | LONG])
1014
+ buttons.push([2, 'Bottom Right', SINGLE | DOUBLE | LONG])
1015
+ buttons.push([3, 'Top Right', SINGLE | DOUBLE | LONG])
1016
+ buttons.push([4, 'Top Left', SINGLE | DOUBLE | LONG])
1017
+ break
1018
+ default:
1019
+ break
1020
+ }
1021
+ break
1022
+ case '_TZ3000_xabckq1v':
1023
+ switch (this.model) {
1024
+ case 'TS004F': // Tuya 4-button switch, single press only
1025
+ buttons.push([1, 'Top Left', SINGLE])
1026
+ buttons.push([2, 'Bottom Left', SINGLE])
1027
+ buttons.push([3, 'Top Right', SINGLE])
1028
+ buttons.push([4, 'Bottom Right', SINGLE])
1029
+ break
1030
+ default:
1031
+ break
1032
+ }
1033
+ break
1034
+ case 'dresden elektronik':
1035
+ switch (this.model) {
1036
+ case 'Kobold':
1037
+ buttons.push([1, 'Button', SINGLE | LONG])
1038
+ break
1039
+ case 'Lighting Switch':
1040
+ if (this.endpoint === '01') {
1041
+ if (this.body.mode !== 2) {
1042
+ gateway.vdebug(
1043
+ '%s: Lighting Switch mode %d instead of 2',
1044
+ this.rpath, this.body.mode
1045
+ )
1046
+ }
1047
+ buttons.push([1, 'Top Left', SINGLE | LONG])
1048
+ buttons.push([2, 'Bottom Left', SINGLE | LONG])
1049
+ buttons.push([3, 'Top Right', SINGLE | LONG])
1050
+ buttons.push([4, 'Bottom Right', SINGLE | LONG])
1051
+ }
1052
+ break
1053
+ case 'Scene Switch':
1054
+ buttons.push([1, 'On', SINGLE | LONG])
1055
+ buttons.push([2, 'Off', SINGLE | LONG])
1056
+ buttons.push([3, 'Scene 1', SINGLE])
1057
+ buttons.push([4, 'Scene 2', SINGLE])
1058
+ buttons.push([5, 'Scene 3', SINGLE])
1059
+ buttons.push([6, 'Scene 4', SINGLE])
1060
+ break
1061
+ default:
1062
+ break
1063
+ }
1064
+ break
1065
+ case 'eWeLink':
1066
+ switch (this.model) {
1067
+ case 'WB01':
1068
+ buttons.push([1, 'Press', SINGLE | DOUBLE | LONG])
1069
+ break
1070
+ default:
1071
+ break
1072
+ }
1073
+ break
1074
+ case 'icasa':
1075
+ switch (this.model) {
1076
+ case 'ICZB-KPD12':
1077
+ case 'ICZB-KPD14S':
1078
+ case 'ICZB-KPD18S':
1079
+ buttons.push([1, 'Off', SINGLE | LONG])
1080
+ buttons.push([2, 'On', SINGLE | LONG])
1081
+ if (this.model !== 'ICZB-KPD12') {
1082
+ buttons.push([3, 'S1', SINGLE])
1083
+ buttons.push([4, 'S2', SINGLE])
1084
+ if (this.model === 'ICZB-KPD18S') {
1085
+ buttons.push([5, 'S3', SINGLE])
1086
+ buttons.push([6, 'S4', SINGLE])
1087
+ buttons.push([7, 'S5', SINGLE])
1088
+ buttons.push([8, 'S6', SINGLE])
1089
+ }
1090
+ }
1091
+ break
1092
+ case 'ICZB-RM11S':
1093
+ buttons.push([1, '1 Off', SINGLE | LONG])
1094
+ buttons.push([2, '1 On', SINGLE | LONG])
1095
+ buttons.push([3, '2 Off', SINGLE | LONG])
1096
+ buttons.push([4, '2 On', SINGLE | LONG])
1097
+ buttons.push([5, '3 Off', SINGLE | LONG])
1098
+ buttons.push([6, '3 On', SINGLE | LONG])
1099
+ buttons.push([7, '4 Off', SINGLE | LONG])
1100
+ buttons.push([8, '4 On', SINGLE | LONG])
1101
+ buttons.push([9, 'S1', SINGLE])
1102
+ buttons.push([10, 'S2', SINGLE])
1103
+ break
1104
+ default:
1105
+ break
1106
+ }
1107
+ break
1108
+ case 'innr':
1109
+ switch (this.model) {
1110
+ case 'RC 110':
1111
+ if (this.endpoint === '01') {
1112
+ buttons.push([1, 'On/Off', SINGLE])
1113
+ buttons.push([2, 'Dim Up', SINGLE | LONG])
1114
+ buttons.push([3, 'Dim Down', SINGLE | LONG])
1115
+ buttons.push([4, '1', SINGLE])
1116
+ buttons.push([5, '2', SINGLE])
1117
+ buttons.push([6, '3', SINGLE])
1118
+ buttons.push([7, '4', SINGLE])
1119
+ buttons.push([8, '5', SINGLE])
1120
+ buttons.push([9, '6', SINGLE])
1121
+ for (let i = 1; i <= 6; i++) {
1122
+ const button = 7 + i * 3
1123
+ buttons.push([button, `On/Off ${i}`, SINGLE])
1124
+ buttons.push([button + 1, `Dim Up ${i}`, SINGLE | LONG])
1125
+ buttons.push([button + 2, `Dim Down ${i}`, SINGLE | LONG])
1126
+ }
1127
+ }
1128
+ break
1129
+ default:
1130
+ break
1131
+ }
1132
+ break
1133
+ case 'lk':
1134
+ switch (this.model) {
1135
+ case 'ZBT-DIMSwitch-D0001': // Linkind 1-Key Remote Control, see #949.
1136
+ buttons.push([1, 'Button', SINGLE | LONG])
1137
+ this.capabilities.homekitValue = (v) => { return 1 }
1138
+ break
1139
+ default:
1140
+ break
1141
+ }
1142
+ break
1143
+ case 'ubisys':
1144
+ switch (this.model) {
1145
+ case 'C4 (5504)':
1146
+ case 'C4-R (5604)':
1147
+ buttons.push([1, '1', SINGLE | LONG])
1148
+ buttons.push([2, '2', SINGLE | LONG])
1149
+ buttons.push([3, '3', SINGLE | LONG])
1150
+ buttons.push([4, '4', SINGLE | LONG])
1151
+ break
1152
+ case 'D1 (5503)':
1153
+ case 'D1-R (5603)':
1154
+ case 'S1-R (5601)':
1155
+ case 'S2 (5502)':
1156
+ case 'S2-R (5602)':
1157
+ buttons.push([1, '1', SINGLE | LONG])
1158
+ buttons.push([2, '2', SINGLE | LONG])
1159
+ break
1160
+ case 'S1 (5501)':
1161
+ buttons.push([1, '1', SINGLE | LONG])
1162
+ break
1163
+ default:
1164
+ break
1165
+ }
1166
+ break
1167
+ default:
1168
+ break
1169
+ }
1170
+ if (buttons.length > 0) {
1171
+ this.capabilities.buttons = {}
1172
+ for (const button of buttons) {
1173
+ this.capabilities.buttons[button[0]] = {
1174
+ label: button[1],
1175
+ events: button[2],
1176
+ hasRepeat: button[3]
1177
+ }
1178
+ }
1179
+ this.capabilities.namespace = dots
1180
+ ? gateway.Characteristics.hap.ServiceLabelNamespace.DOTS
1181
+ : gateway.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
1182
+ }
1183
+ }
1184
+
1185
+ /** Patch a resource corresponding to a `Thermostat` service.
1186
+ * @param {DeconzAccessory.Gateway} gateway - The gateway.
1187
+ */
1188
+ patchThermostat (gateway) {
1189
+ if (this.manufacturer === 'ELKO' && this.model === 'Super TR') {
1190
+ this.capabilities.heatValue = 'heat'
1191
+ } else {
1192
+ this.capabilities.heatValue = 'auto'
1193
+ }
1194
+ }
1195
+
1196
+ /** Patch a resource corresponding to a `WindowCovering` service.
1197
+ * @param {DeconzAccessory.Gateway} gateway - The gateway.
1198
+ */
1199
+ patchWindowCovering (gateway) {
1200
+ if (this.manufacturer === 'LUMI' && this.model === 'lumi.curtain.acn002') {
1201
+ this.capabilities.maxSpeed = 2
1202
+ this.capabilities.positionChange = true
1203
+ }
1204
+ }
479
1205
  }
480
1206
 
481
1207
  module.exports = Resource