homebridge-deconz 1.0.0 → 1.0.2

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.
@@ -28,13 +28,24 @@ class Device {
28
28
  */
29
29
  this.id = resource.id
30
30
 
31
- /** Zigbee device vs virtual device.
31
+ /** The key of the delegate for the primary resource for the device in
32
+ * {@link DeconzDevice#resourceBySubtype resourceBySubtype}
32
33
  *
33
- * This is the {@link Deconz.Resource#zigbee zigbee} of the
34
- * delegates of all resources for the device.
35
- * @type {boolean}
34
+ * This is the {@link DeconzDevice.Resource#subtype subtype} of the
35
+ * HomeKit service corresponding to the primary resource.
36
+ * @type {string}
36
37
  */
37
- this.zigbee = resource.zigbee
38
+ this.primary = resource.subtype
39
+
40
+ /** An array of keys of the delegates for the resources for the device in
41
+ * {@link DeconzDevice#resourceBySubtype resourceBySubtype} by service name.
42
+ *
43
+ * These are the {@link DeconzDevice.Resource#subtype subtype} values of the
44
+ * HomeKit service corresponding to the resource.
45
+ * @type {Object.<string, Array.<string>>}
46
+ */
47
+ this.subtypesByServiceName = {}
48
+ this.subtypesByServiceName[resource.serviceName] = [resource.subtype]
38
49
 
39
50
  /** The delegates of the resources for the device, by subtype of the
40
51
  * corresponding HomeKit service.
@@ -43,14 +54,13 @@ class Device {
43
54
  this.resourceBySubtype = {}
44
55
  this.resourceBySubtype[resource.subtype] = resource
45
56
 
46
- /** The key of the delegate for the primary resource for the device in
47
- * {@link DeconzDevice#resourceBySubtype resourceBySubtype}
57
+ /** Zigbee device vs virtual device.
48
58
  *
49
- * This is the {@link DeconzDevice.Resource#subtype subtype} of the
50
- * HomeKit service corresponding to the primary resource.
51
- * @type {string}
59
+ * This is the {@link Deconz.Resource#zigbee zigbee} of the
60
+ * delegates of all resources for the device.
61
+ * @type {boolean}
52
62
  */
53
- this.primary = resource.subtype
63
+ this.zigbee = resource.zigbee
54
64
  }
55
65
 
56
66
  /** The delegate of the primary resource of the device.
@@ -89,6 +99,11 @@ class Device {
89
99
  `${resource.rpath}: cannot combine ${r.rpath}`
90
100
  )
91
101
  }
102
+ if (this.subtypesByServiceName[resource.serviceName] == null) {
103
+ this.subtypesByServiceName[resource.serviceName] = [resource.subtype]
104
+ } else {
105
+ this.subtypesByServiceName[resource.serviceName].push(resource.subtype)
106
+ }
92
107
  this.resourceBySubtype[subtype] = resource
93
108
  const p = this.resourceBySubtype[this.primary]
94
109
  if (p.rtype === rtype && p.prio < prio) {
@@ -145,6 +145,11 @@ class Resource {
145
145
  */
146
146
  this.endpoint = endpoint
147
147
 
148
+ /** Zigbee cluster
149
+ * @type {string}
150
+ */
151
+ this.cluster = cluster
152
+
148
153
  /** Zigbee device vs virtual device.
149
154
  *
150
155
  * Derived from the resource type and, for `sensors`, on the `type` in the
@@ -301,6 +306,7 @@ class Resource {
301
306
  case 'LightLevel': return 'LightLevel'
302
307
  case 'Moisture': return 'Humidity'
303
308
  case 'OpenClose': return 'Contact'
309
+ case 'ParticulateMatter': return 'AirQuality'
304
310
  case 'Power': return 'Power'
305
311
  case 'Presence': return 'Motion'
306
312
  case 'Pressure': return 'AirPressure'
@@ -482,14 +488,23 @@ class Resource {
482
488
  buttons.push([3, 'Previous', SINGLE | LONG])
483
489
  buttons.push([4, 'Next', SINGLE | LONG])
484
490
  break
491
+ case 'RODRET Dimmer':
492
+ buttons.push([1, 'Dim Up', SINGLE | LONG])
493
+ buttons.push([2, 'Dim Down', SINGLE | LONG])
494
+ break
485
495
  case 'SYMFONISK Sound Controller':
486
- buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
487
- if (this.body.mode === 1) {
488
- buttons.push([2, 'Turn Right', LONG])
489
- buttons.push([3, 'Turn Left', LONG])
490
- } else {
491
- buttons.push([2, 'Turn Right', SINGLE])
492
- buttons.push([3, 'Turn Left', SINGLE])
496
+ if (this.cluster === '1000') {
497
+ buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
498
+ if (this.body.mode === 1) {
499
+ buttons.push([2, 'Turn Right', LONG])
500
+ buttons.push([3, 'Turn Left', LONG])
501
+ } else {
502
+ buttons.push([2, 'Turn Right', SINGLE])
503
+ buttons.push([3, 'Turn Left', SINGLE])
504
+ }
505
+ } else if (this.cluster === '0008') { // ZHARelativeRotary
506
+ // buttons.push([4, 'Turn Right', SINGLE])
507
+ // buttons.push([5, 'Turn Left', SINGLE])
493
508
  }
494
509
  break
495
510
  case 'SYMFONISK sound remote gen2':
@@ -754,7 +769,7 @@ class Resource {
754
769
  buttons.push([2, '2', SINGLE | LONG, true])
755
770
  buttons.push([3, '3', SINGLE | LONG, true])
756
771
  buttons.push([4, '4', SINGLE | LONG, true])
757
- } else if (this.endpoint === '14') {
772
+ } else if (this.endpoint === '14') { // ZHARelativeRotary
758
773
  buttons.push([5, 'Right Turn', SINGLE])
759
774
  buttons.push([6, 'Left Turn', SINGLE])
760
775
  }
@@ -82,6 +82,12 @@ class Gateway extends AccessoryDelegate {
82
82
  this.client.apiKey = value
83
83
  })
84
84
 
85
+ this.addPropertyDelegate({
86
+ key: 'autoExpose',
87
+ value: true,
88
+ silent: true
89
+ })
90
+
85
91
  this.addPropertyDelegate({
86
92
  key: 'brightnessAdjustment',
87
93
  value: 1,
@@ -495,7 +501,7 @@ class Gateway extends AccessoryDelegate {
495
501
  */
496
502
  async connect (retry = 0) {
497
503
  if (!this.values.expose) {
498
- this.warn('unlock gateway and set Expose to obtain an API key')
504
+ this.warn('unlock gateway and set expose to obtain an API key')
499
505
  return
500
506
  }
501
507
  try {
@@ -782,10 +788,12 @@ class Gateway extends AccessoryDelegate {
782
788
  name: this.name,
783
789
  settings: this.values.apiKey == null
784
790
  ? {
791
+ autoExpose: this.values.autoExpose,
785
792
  expose: this.values.expose,
786
793
  logLevel: this.values.logLevel
787
794
  }
788
795
  : {
796
+ autoExpose: this.values.autoExpose,
789
797
  brightnessAdjustment: this.values.brightnessAdjustment * 100,
790
798
  expose: this.values.expose,
791
799
  exposeSchedules: this.values.exposeSchedules,
@@ -842,6 +850,7 @@ class Gateway extends AccessoryDelegate {
842
850
  .on('userInputError', (error) => {
843
851
  this.warn(error)
844
852
  })
853
+ .boolKey('autoExpose')
845
854
  .boolKey('expose')
846
855
  .intKey('logLevel', 0, 3)
847
856
  if (this.values.apiKey != null) {
@@ -863,6 +872,7 @@ class Gateway extends AccessoryDelegate {
863
872
  this.values[key] = settings[key] / 100
864
873
  responseBody[key] = this.values[key]
865
874
  break
875
+ case 'autoExpose':
866
876
  case 'expose':
867
877
  case 'exposeSchedules':
868
878
  case 'heartrate':
@@ -1105,7 +1115,7 @@ class Gateway extends AccessoryDelegate {
1105
1115
  try {
1106
1116
  const { id, zigbee } = this.deviceByRidByRtype[rtype][rid]
1107
1117
  if (this.context.settingsById[id] == null) {
1108
- this.context.settingsById[id] = { expose: zigbee }
1118
+ this.context.settingsById[id] = { expose: zigbee && this.values.autoExpose }
1109
1119
  }
1110
1120
  if (this.context.settingsById[id].expose) {
1111
1121
  if (this.accessoryById[id] == null) {
@@ -33,19 +33,8 @@ class Light extends DeconzAccessory {
33
33
  serviceName: this.values.serviceName
34
34
  })
35
35
 
36
- for (const subtype in device.resourceBySubtype) {
37
- const resource = device.resourceBySubtype[subtype]
38
- if (subtype === device.primary) {
39
- continue
40
- }
41
- if (resource.rtype === 'lights') {
42
- this.createService(resource, { serviceName: this.values.serviceName })
43
- } else {
44
- this.createService(resource)
45
- }
46
- }
47
-
48
36
  const params = {}
37
+
49
38
  if (this.values.serviceName === 'Outlet') {
50
39
  this.service.addCharacteristicDelegate({
51
40
  key: 'lockPhysicalControls',
@@ -57,7 +46,14 @@ class Light extends DeconzAccessory {
57
46
  Characteristic: this.Characteristics.eve.LastActivation,
58
47
  silent: true
59
48
  })
60
- } else {
49
+ } else if (this.values.serviceName === 'Switch') {
50
+ params.onDelegate = this.service.characteristicDelegate('on')
51
+ params.lastOnDelegate = this.service.addCharacteristicDelegate({
52
+ key: 'lastActivation',
53
+ Characteristic: this.Characteristics.eve.LastActivation,
54
+ silent: true
55
+ })
56
+ } else if (this.values.serviceName === 'Light') {
61
57
  params.lightOnDelegate = this.service.characteristicDelegate('on')
62
58
  params.lastLightOnDelegate = this.service.addCharacteristicDelegate({
63
59
  key: 'lastActivation',
@@ -65,6 +61,7 @@ class Light extends DeconzAccessory {
65
61
  silent: true
66
62
  })
67
63
  }
64
+
68
65
  if (this.service.characteristicDelegate('totalConsumption') != null) {
69
66
  params.totalConsumptionDelegate = this.service.characteristicDelegate('totalConsumption')
70
67
  if (this.service.values.consumption === undefined) {
@@ -86,6 +83,51 @@ class Light extends DeconzAccessory {
86
83
  }
87
84
  this.historyService = new ServiceDelegate.History(this, params)
88
85
 
86
+ for (const subtype in device.resourceBySubtype) {
87
+ const resource = device.resourceBySubtype[subtype]
88
+ if (subtype === device.primary) {
89
+ continue
90
+ }
91
+ if (resource.rtype === 'lights') {
92
+ const service = this.createService(resource, { serviceName: this.values.serviceName })
93
+ if (this.values.serviceName !== 'Valve') {
94
+ this.historyService.addLastOnDelegate(
95
+ service.characteristicDelegate('on'),
96
+ service.addCharacteristicDelegate({
97
+ key: 'lastActivation',
98
+ Characteristic: this.Characteristics.eve.LastActivation,
99
+ silent: true
100
+ })
101
+ )
102
+ }
103
+ } else {
104
+ this.createService(resource)
105
+ }
106
+ }
107
+
108
+ if (
109
+ this.values.serviceName === 'Outlet' &&
110
+ this.service.characteristicDelegate('totalConsumption') == null &&
111
+ this.service.characteristicDelegate('consumption') == null &&
112
+ this.servicesByServiceName.Outlet?.length === 1
113
+ ) {
114
+ // Dumb Outlet
115
+ this.warn('dumb outlet')
116
+ this.service.addCharacteristicDelegate({
117
+ key: 'dummyTotalConsumption',
118
+ Characteristic: this.Characteristics.eve.TotalConsumption,
119
+ props: {
120
+ perms: [
121
+ this.Characteristic.Perms.PAIRED_READ,
122
+ this.Characteristic.Perms.NOTIFY,
123
+ this.Characteristic.Perms.HIDDEN
124
+ ]
125
+ },
126
+ silent: true,
127
+ value: 0
128
+ })
129
+ }
130
+
89
131
  setImmediate(() => {
90
132
  this.debug('initialised')
91
133
  this.emit('initialised')
@@ -106,15 +106,39 @@ class Sensor extends DeconzAccessory {
106
106
  params.vocDensityDelegate = service.characteristicDelegate('vocDensity')
107
107
  }
108
108
  }
109
- if (this.servicesByServiceName.Flag != null) {
109
+ if (this.servicesByServiceName.Flag?.length === 1) {
110
110
  const service = this.servicesByServiceName.Flag[0]
111
- params.switchOnDelegate = service.characteristicDelegate('on')
112
- params.lastSwitchOnDelegate = service.addCharacteristicDelegate({
111
+ params.onDelegate = service.characteristicDelegate('on')
112
+ params.lastOnDelegate = service.addCharacteristicDelegate({
113
113
  key: 'lastActivation',
114
114
  Characteristic: this.Characteristics.eve.LastActivation,
115
115
  silent: true
116
116
  })
117
117
  }
118
+ if (
119
+ params.temperatureDelegate != null && params.humidityDelegate != null &&
120
+ params.airPressureDelegate == null && params.vocDensityDelegate == null &&
121
+ this.servicesByServiceName.Battery?.length === 1
122
+ ) {
123
+ // Eve would see this as an Eve Thermo Control.
124
+ this.airPressureService = new ServiceDelegate(this, {
125
+ name: this.name + ' Pressure',
126
+ Service: this.Services.eve.AirPressureSensor,
127
+ hidden: true
128
+ })
129
+ this.airPressureService.addCharacteristicDelegate({
130
+ key: 'airPressure',
131
+ Characteristic: this.Characteristics.eve.AirPressure,
132
+ props: {
133
+ perms: [
134
+ this.Characteristic.Perms.PAIRED_READ,
135
+ this.Characteristic.Perms.NOTIFY,
136
+ this.Characteristic.Perms.HIDDEN
137
+ ]
138
+ },
139
+ value: 0
140
+ })
141
+ }
118
142
  if (Object.keys(params).length > 0) {
119
143
  this.historyService = new ServiceDelegate.History(this, params)
120
144
  }
@@ -35,8 +35,8 @@ class WarningDevice extends DeconzAccessory {
35
35
 
36
36
  const params = {}
37
37
  if (this.servicesByServiceName.WarningDevice?.length === 1) {
38
- params.switchOnDelegate = this.service.characteristicDelegate('on')
39
- params.lastSwitchOnDelegate = this.service.addCharacteristicDelegate({
38
+ params.onDelegate = this.service.characteristicDelegate('on')
39
+ params.lastOnDelegate = this.service.addCharacteristicDelegate({
40
40
  key: 'lastActivation',
41
41
  Characteristic: this.Characteristics.eve.LastActivation,
42
42
  silent: true
@@ -130,6 +130,7 @@ class DeconzAccessory extends AccessoryDelegate {
130
130
  this.values.firmware, this.rpaths.length
131
131
  )
132
132
  this.debug('%d resources: %s', this.rpaths.length, this.rpaths.join(', '))
133
+ this.vdebug('device: %j', this.device)
133
134
  if (this.service != null) {
134
135
  await this.service.identify()
135
136
  }
@@ -177,22 +178,26 @@ class DeconzAccessory extends AccessoryDelegate {
177
178
  service.addResource(resource)
178
179
  }
179
180
  } else if (params.serviceName === 'Label') {
181
+ service = this.servicesByServiceName.Label?.[0]
180
182
  // Default button
181
183
  if (resource.capabilities.buttons == null) {
182
- this.warn(
183
- '%s: unknown %s: %j', resource.rpath, resource.body.type,
184
- resource.body
185
- )
186
- resource.capabilities.buttons = {
187
- 1: {
188
- label: 'Unknown Button',
189
- events: SINGLE | DOUBLE | LONG
184
+ if (service == null) {
185
+ this.warn(
186
+ '%s: unknown %s: %j', resource.rpath, resource.body.type,
187
+ resource.body
188
+ )
189
+ resource.capabilities.buttons = {
190
+ 1: {
191
+ label: 'Unknown Button',
192
+ events: SINGLE | DOUBLE | LONG
193
+ }
190
194
  }
195
+ resource.capabilities.namespace =
196
+ this.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
197
+ } else {
198
+ resource.capabilities.buttons = {}
191
199
  }
192
- resource.capabilities.namespace =
193
- this.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
194
200
  }
195
- service = this.servicesByServiceName.Label?.[0]
196
201
  }
197
202
  if (service == null) {
198
203
  service = new DeconzService[params.serviceName](this, resource, {
@@ -22,16 +22,64 @@ class AirQuality extends DeconzService.SensorsResource {
22
22
  })
23
23
  }
24
24
 
25
- if (resource.body.state.airqualityppb !== undefined) {
25
+ if (resource.body.state.measured_value !== undefined) {
26
+ const cmv = resource.body.capabilities.measured_value
27
+ switch (cmv.substance) {
28
+ case 'PM2.5':
29
+ if (cmv.quantity !== 'density') {
30
+ service.warn('%s: unsupported substance', cmv.quantity)
31
+ break
32
+ }
33
+ if (cmv.unit !== 'ug/m^3') {
34
+ service.warn('%s: unsupported unit', cmv.unit)
35
+ break
36
+ }
37
+ service.addCharacteristicDelegate({
38
+ key: 'pm25Density',
39
+ Characteristic: service.Characteristics.hap.PM2_5Density,
40
+ unit: ' µg/m³',
41
+ props: { minValue: cmv.min, maxValue: cmv.max }
42
+ })
43
+ service.resources[resource.rpath] = {
44
+ key: 'pm25Density',
45
+ f: (v) => { return v }
46
+ }
47
+ break
48
+ case 'tVOC':
49
+ if (cmv.quantity !== 'level') {
50
+ service.warn('%s: unsupported substance', cmv.quantity)
51
+ break
52
+ }
53
+ if (cmv.unit !== 'ppb') {
54
+ service.warn('%s: unsupported unit', cmv.unit)
55
+ break
56
+ }
57
+ service.addCharacteristicDelegate({
58
+ key: 'vocDensity',
59
+ Characteristic: service.Characteristics.hap.VOCDensity,
60
+ unit: ' µg/m³',
61
+ props: {
62
+ minValue: Math.floor(cmv.min * 4.57),
63
+ maxValue: Math.ceil(cmv.max * 4.57)
64
+ }
65
+ })
66
+ service.resources[resource.rpath] = {
67
+ key: 'vocDensity',
68
+ f: (v) => { return Math.round(v * 4.57) }
69
+ }
70
+ break
71
+ default:
72
+ service.warn('%s: unsupported substance', cmv.substance)
73
+ break
74
+ }
75
+ } else if (resource.body.state.airqualityppb !== undefined) {
26
76
  service.addCharacteristicDelegate({
27
77
  key: 'vocDensity',
28
78
  Characteristic: service.Characteristics.hap.VOCDensity,
29
79
  unit: ' µg/m³',
30
80
  props: { minValue: 0, maxValue: 65535, minStep: 1 }
31
81
  })
32
- }
33
-
34
- if (resource.body.state.pm2_5 !== undefined) {
82
+ } else if (resource.body.state.pm2_5 !== undefined) {
35
83
  service.addCharacteristicDelegate({
36
84
  key: 'pm25Density',
37
85
  Characteristic: service.Characteristics.hap.PM2_5Density,
@@ -68,16 +116,27 @@ class AirQuality extends DeconzService.SensorsResource {
68
116
  }
69
117
  }
70
118
 
71
- static updateResourceState (service, state) {
72
- if (state.airqualityppb != null) {
119
+ static updateResourceState (service, state, rpath) {
120
+ if (state.measured_value != null && service.resources[rpath] != null) {
121
+ const { key, f } = service.resources[rpath]
122
+ service.values[key] = f(state.measured_value)
123
+ if (
124
+ state.airquality != null && (
125
+ key === 'vocDensity' || service.values.vocDensity === undefined
126
+ )
127
+ ) {
128
+ service.values.airQuality = AirQuality.airQualityValue(
129
+ service, state.airquality
130
+ )
131
+ }
132
+ } else if (state.airqualityppb != null) {
73
133
  service.values.vocDensity = Math.round(state.airqualityppb * 4.57)
74
134
  if (state.airquality != null) {
75
135
  service.values.airQuality = AirQuality.airQualityValue(
76
136
  service, state.airquality
77
137
  )
78
138
  }
79
- }
80
- if (state.pm2_5 != null) {
139
+ } else if (state.pm2_5 != null) {
81
140
  service.values.pm25Density = state.pm2_5
82
141
  if (state.airquality != null && service.values.vocDensity === undefined) {
83
142
  service.values.airQuality = AirQuality.airQualityValue(
@@ -93,6 +152,7 @@ class AirQuality extends DeconzService.SensorsResource {
93
152
  constructor (accessory, resource, params = {}) {
94
153
  params.Service = accessory.Services.hap.AirQualitySensor
95
154
  super(accessory, resource, params)
155
+ this.resources = {}
96
156
 
97
157
  AirQuality.addResource(this, resource)
98
158
 
@@ -101,8 +161,8 @@ class AirQuality extends DeconzService.SensorsResource {
101
161
  this.update(resource.body, resource.rpath)
102
162
  }
103
163
 
104
- updateState (state) {
105
- AirQuality.updateResourceState(this, state)
164
+ updateState (state, rpath) {
165
+ AirQuality.updateResourceState(this, state, rpath)
106
166
  super.updateState(state)
107
167
  }
108
168
  }
@@ -20,6 +20,8 @@ class Contact extends DeconzService.SensorsResource {
20
20
  Characteristic: this.Characteristics.hap.ContactSensorState
21
21
  })
22
22
 
23
+ // With _Status Tapered_ Eve thinks the _Door Sensor_ is an Eve Windows Guard
24
+ // (instead of an Eve Door & Window) and won't display it.
23
25
  this.addCharacteristicDelegates({ noTampered: true })
24
26
 
25
27
  this.update(resource.body, resource.rpath)
@@ -27,10 +27,11 @@ class Humidity extends DeconzService.SensorsResource {
27
27
  }
28
28
 
29
29
  updateState (state) {
30
- if (state.humidity != null) {
30
+ if (state.measured_value != null) {
31
+ this.values.humidity = Math.round(state.measured_value * 10) / 10
32
+ } else if (state.humidity != null) {
31
33
  this.values.humidity = Math.round(state.humidity / 10) / 10
32
- }
33
- if (state.moisture != null) {
34
+ } else if (state.moisture != null) {
34
35
  this.values.humidity = Math.round(state.moisture / 10) / 10
35
36
  }
36
37
  super.updateState(state)
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const DeconzService = require('.')
8
+ const DeconzService = require('../DeconzService')
9
9
 
10
10
  /**
11
11
  * @memberof DeconzService
@@ -32,13 +32,14 @@ class Label extends DeconzService.SensorsResource {
32
32
  if (resource.body.type.endsWith('Switch')) {
33
33
  this.buttonResources[resource.rpath] = {
34
34
  buttonEvent: resource.body.state.buttonevent,
35
- lastUpdated: '',
35
+ lastUpdated: new Date(),
36
36
  toButtonEvent: resource.capabilities.toButtonEvent
37
37
  }
38
38
  } else if (resource.body.type.endsWith('RelativeRotary')) {
39
39
  const keys = Object.keys(resource.capabilities.buttons)
40
40
  this.buttonResources[resource.rpath] = {
41
- lastUpdated: '',
41
+ expectedRotation: resource.body.state.expectedrotation,
42
+ lastUpdated: new Date(),
42
43
  right: keys[0],
43
44
  left: keys[1]
44
45
  }
@@ -64,13 +65,9 @@ class Label extends DeconzService.SensorsResource {
64
65
  this.gateway.reExposeAccessory(this.accessory.id)
65
66
  return
66
67
  }
67
- if (buttonResource.lastUpdated === '') {
68
- buttonResource.lastUpdated = state.lastupdated
69
- } else if (
70
- state.lastupdated != null &&
71
- state.lastupdated !== buttonResource.lastUpdated
72
- ) {
73
- buttonResource.lastUpdated = state.lastupdated
68
+ const lastUpdated = new Date(state.lastupdated + 'Z')
69
+ if (lastUpdated > buttonResource.lastUpdated) {
70
+ buttonResource.lastUpdated = lastUpdated
74
71
  if (buttonResource.buttonEvent !== undefined) {
75
72
  const oldValue = buttonResource.buttonEvent
76
73
  if (state.buttonevent != null) {
@@ -85,10 +82,13 @@ class Label extends DeconzService.SensorsResource {
85
82
  false // this.accessoryDelegate.settingsService.values.repeat
86
83
  )
87
84
  } else {
88
- const i = state.expectedrotation >= 0
85
+ if (state.expectedrotation != null) {
86
+ buttonResource.expectedRotation = state.expectedrotation
87
+ }
88
+ const i = buttonResource.expectedRotation >= 0
89
89
  ? buttonResource.right
90
90
  : buttonResource.left
91
- this.buttonServices[i].updateRotation()
91
+ this.buttonServices[i]?.updateRotation()
92
92
  }
93
93
  }
94
94
  super.updateState(state)
@@ -43,7 +43,7 @@ class Light extends DeconzService.LightsResource {
43
43
  : this.resource.body.state.all_on
44
44
  }).on('didSet', (value, fromHomeKit) => {
45
45
  if (fromHomeKit) {
46
- this.put({ on: value })
46
+ this.putState({ on: value })
47
47
  this.updateAdaptiveLighting()
48
48
  }
49
49
  })
@@ -55,7 +55,7 @@ class Light extends DeconzService.LightsResource {
55
55
  value: this.resource.body.state.any_on
56
56
  }).on('didSet', (value, fromHomeKit) => {
57
57
  if (fromHomeKit) {
58
- this.put({ on: value })
58
+ this.putState({ on: value })
59
59
  this.updateAdaptiveLighting()
60
60
  }
61
61
  })
@@ -70,7 +70,7 @@ class Light extends DeconzService.LightsResource {
70
70
  }).on('didSet', (value, fromHomeKit) => {
71
71
  if (fromHomeKit) {
72
72
  const bri = Math.round(value * 2.54)
73
- this.put({ bri })
73
+ this.putState({ bri })
74
74
  this.updateAdaptiveLighting()
75
75
  }
76
76
  })
@@ -80,7 +80,7 @@ class Light extends DeconzService.LightsResource {
80
80
  Characteristic: this.Characteristics.my.BrightnessChange,
81
81
  value: 0
82
82
  }).on('didSet', async (value) => {
83
- this.put({ bri_inc: Math.round(value * 254.0 / 100.0) })
83
+ this.putState({ bri_inc: Math.round(value * 254.0 / 100.0) })
84
84
  await timeout(this.platform.config.waitTimeReset)
85
85
  this.values.brightnessChange = 0
86
86
  })
@@ -104,6 +104,10 @@ class Light extends DeconzService.LightsResource {
104
104
  this.addCharacteristicDelegate({
105
105
  key: 'colorExecuteIfOff',
106
106
  value: this.resource.body.config.color.execute_if_off
107
+ }).on('didSet', async (value) => {
108
+ try {
109
+ await this.put('/config', { color: { execute_if_off: value } })
110
+ } catch (error) { this.warn(error) }
107
111
  })
108
112
  }
109
113
 
@@ -131,7 +135,7 @@ class Light extends DeconzService.LightsResource {
131
135
  const ct = Math.max(ctMin, Math.min(value, ctMax))
132
136
  if (fromHomeKit) {
133
137
  this.checkAdaptiveLighting()
134
- this.put({ ct })
138
+ this.putState({ ct })
135
139
  this.values.colormode = 'ct'
136
140
  }
137
141
  if (this.capabilities.xy && this.values.colormode === 'ct') {
@@ -146,7 +150,9 @@ class Light extends DeconzService.LightsResource {
146
150
  if (
147
151
  this.resource.body?.capabilities?.color?.xy?.blue == null ||
148
152
  this.resource.body?.capabilities?.color?.xy?.green == null ||
149
- this.resource.body?.capabilities?.color?.xy?.red == null
153
+ this.resource.body?.capabilities?.color?.xy?.red == null ||
154
+ this.resource.body?.capabilities?.color?.xy?.green?.[1] === 0 ||
155
+ this.resource.body?.capabilities?.color?.xy?.red?.[0] === 0
150
156
  ) {
151
157
  if (this.capabilities.on) {
152
158
  this.warn('using default xy gamut')
@@ -169,7 +175,7 @@ class Light extends DeconzService.LightsResource {
169
175
  const xy = hsvToXy(
170
176
  value, this.values.saturation, this.capabilities.gamut
171
177
  )
172
- this.put({ xy })
178
+ this.putState({ xy })
173
179
  this.values.colormode = 'xy'
174
180
  }
175
181
  })
@@ -180,7 +186,7 @@ class Light extends DeconzService.LightsResource {
180
186
  }).on('didSet', (value, fromHomeKit) => {
181
187
  if (fromHomeKit) {
182
188
  const xy = hsvToXy(this.values.hue, value, this.capabilities.gamut)
183
- this.put({ xy })
189
+ this.putState({ xy })
184
190
  this.values.colormode = 'xy'
185
191
  }
186
192
  })
@@ -192,7 +198,7 @@ class Light extends DeconzService.LightsResource {
192
198
  }).on('didSet', (value, fromHomeKit) => {
193
199
  if (fromHomeKit) {
194
200
  const hue = Math.round(this.values.hue * 65535.0 / 360.0)
195
- this.put({ hue })
201
+ this.putState({ hue })
196
202
  this.values.colormode = 'hs'
197
203
  }
198
204
  })
@@ -203,7 +209,7 @@ class Light extends DeconzService.LightsResource {
203
209
  }).on('didSet', (value, fromHomeKit) => {
204
210
  if (fromHomeKit) {
205
211
  const sat = Math.round(this.values.saturation * 254.0 / 100.0)
206
- this.put({ sat })
212
+ this.putState({ sat })
207
213
  this.values.colormode = 'hs'
208
214
  }
209
215
  })
@@ -220,7 +226,7 @@ class Light extends DeconzService.LightsResource {
220
226
  if (value) {
221
227
  state.colorloopspeed = this.values.colorLoopSpeed
222
228
  }
223
- this.put(state)
229
+ this.putState(state)
224
230
  this.values.colormode = 'hs'
225
231
  }
226
232
  })
@@ -232,7 +238,7 @@ class Light extends DeconzService.LightsResource {
232
238
  }).on('didSet', (value, fromHomeKit) => {
233
239
  if (fromHomeKit) {
234
240
  const effect = 'colorloop'
235
- this.put({ effect, colorloopspeed: value })
241
+ this.putState({ effect, colorloopspeed: value })
236
242
  this.values.colormode = 'hs'
237
243
  }
238
244
  })
@@ -328,7 +334,7 @@ class Light extends DeconzService.LightsResource {
328
334
  }).on('didSet', (value, fromHomeKit) => {
329
335
  if (fromHomeKit) {
330
336
  this.checkAdaptiveLighting()
331
- this.put({ effect: value ? effect : 'none' })
337
+ this.putState({ effect: value ? effect : 'none' })
332
338
  this.values.effectString = value ? effect : 'none'
333
339
  }
334
340
  })
@@ -492,10 +498,8 @@ class Light extends DeconzService.LightsResource {
492
498
  this.checkAdaptiveLighting()
493
499
  if (fromHomeKit && value) {
494
500
  try {
495
- const path = this.resource.rpath + '/scenes/' + scene.id + '/recall'
496
- this.debug('PUT %s', path)
497
- await this.client.put(path)
498
- } catch (error) { this.error(error) }
501
+ await this.put('/scenes/' + scene.id + '/recall')
502
+ } catch (error) { this.warn(error) }
499
503
  await timeout(this.platform.config.waitTimeReset)
500
504
  service.values.on = false
501
505
  }
@@ -560,7 +564,7 @@ class Light extends DeconzService.LightsResource {
560
564
  return
561
565
  }
562
566
  this.debug('adaptive lighting: set Color Temperature to %d mired', ct)
563
- this.put({ ct })
567
+ this.putState({ ct })
564
568
  this.fromAdaptiveLighting = true
565
569
  this.values.colormode = 'ct'
566
570
  if (ct !== this.values.colorTemperature) {
@@ -6,16 +6,13 @@
6
6
  'use strict'
7
7
 
8
8
  const { timeout } = require('homebridge-lib')
9
- const { ApiClient } = require('hb-deconz-tools')
10
9
  const DeconzService = require('../DeconzService')
11
10
 
12
- const { HttpError } = ApiClient
13
-
14
11
  class LightsResource extends DeconzService {
15
12
  constructor (accessory, resource, params) {
16
13
  super(accessory, resource, params)
17
14
  this.stateKey = resource.rtype === 'groups' ? 'action' : 'state'
18
- this.rpathState = this.rpath + '/' + this.stateKey
15
+ this.statePath = '/' + this.stateKey
19
16
 
20
17
  this.updating = 0
21
18
  this.targetState = {}
@@ -49,28 +46,25 @@ class LightsResource extends DeconzService {
49
46
  }
50
47
 
51
48
  async identify () {
52
- if (this.capabilities.alert) {
53
- if (this.capabilities.breathe) {
54
- await this.put({ alert: 'breathe' })
55
- await timeout(1000)
56
- await this.put({ alert: 'stop' })
57
- } else {
58
- await this.put({ alert: 'select' })
59
- }
49
+ if (this.resource.body?.capabilities?.alerts?.includes('breathe')) {
50
+ await this.put(this.statePath, { alert: 'breathe' })
51
+ await timeout(1000)
52
+ return this.put(this.statePath, { alert: 'finish' })
60
53
  }
54
+ return this.put(this.statePath, { alert: 'select' })
61
55
  }
62
56
 
63
- // Collect changes into a combined request.
64
- async put (state) {
57
+ // Collect state changes into a combined request.
58
+ async putState (state) {
65
59
  for (const key in state) {
66
60
  this.resource.body[this.stateKey][key] = state[key]
67
61
  this.targetState[key] = state[key]
68
62
  }
69
- return this._put()
63
+ return this._putState()
70
64
  }
71
65
 
72
- // Send the request (for the combined changes) to the gateway.
73
- async _put () {
66
+ // Send the request (for the combined state changes) to the gateway.
67
+ async _putState () {
74
68
  try {
75
69
  if (this.platform.config.waitTimeUpdate > 0) {
76
70
  this.updating++
@@ -88,7 +82,7 @@ class LightsResource extends DeconzService {
88
82
  targetState.transitiontime = this.gateway.transitionTime * 10
89
83
  this.gateway.resetTransitionTime()
90
84
  }
91
- if (this.capabilities.noTransition) {
85
+ if (this.resource.body?.capabilities?.transition_block) {
92
86
  if (
93
87
  (
94
88
  targetState.on != null || targetState.bri != null ||
@@ -102,16 +96,11 @@ class LightsResource extends DeconzService {
102
96
  targetState.transitiontime = 0
103
97
  }
104
98
  }
105
- this.debug('PUT %s %j', this.rpathState, targetState)
106
- await this.client.put(this.rpathState, targetState)
99
+ await this.put(this.statePath, targetState)
107
100
  this.recentlyUpdated = true
108
101
  await timeout(500)
109
102
  this.recentlyUpdated = false
110
- } catch (error) {
111
- if (!(error instanceof HttpError)) {
112
- this.warn(error)
113
- }
114
- }
103
+ } catch (error) { this.warn(error) }
115
104
  }
116
105
  }
117
106
 
@@ -63,9 +63,6 @@ class Motion extends DeconzService.SensorsResource {
63
63
  if (state.vibration != null) {
64
64
  this.values.motion = state.vibration
65
65
  }
66
- if (state.tampered != null) {
67
- this.values.tampered = state.tampered
68
- }
69
66
  super.updateState(state)
70
67
  }
71
68
 
@@ -20,7 +20,7 @@ class Outlet extends DeconzService.LightsResource {
20
20
  : this.resource.body.state.all_on
21
21
  }).on('didSet', (value, fromHomeKit) => {
22
22
  if (fromHomeKit) {
23
- this.put({ on: value })
23
+ this.putState({ on: value })
24
24
  }
25
25
  })
26
26
 
@@ -31,7 +31,7 @@ class Outlet extends DeconzService.LightsResource {
31
31
  value: this.resource.body.state.any_on
32
32
  }).on('didSet', (value, fromHomeKit) => {
33
33
  if (fromHomeKit) {
34
- this.put({ on: value })
34
+ this.putState({ on: value })
35
35
  }
36
36
  })
37
37
  }
@@ -42,13 +42,14 @@ class Outlet extends DeconzService.LightsResource {
42
42
  value: 1 // Eve interpretes OutletInUse as: device is physically plugged in.
43
43
  })
44
44
 
45
- this.addCharacteristicDelegates()
46
-
47
- this.settings = {
48
- resetTimeout: this.platform.config.resetTimeout,
49
- waitTimeUpdate: this.platform.config.waitTimeUpdate,
50
- wallSwitch: false
45
+ if (this.resource.rtype === 'lights') {
46
+ this.addCharacteristicDelegate({
47
+ key: 'wallSwitch',
48
+ value: false
49
+ })
51
50
  }
51
+
52
+ this.addCharacteristicDelegates()
52
53
  }
53
54
 
54
55
  updateState (state) {
@@ -63,6 +64,13 @@ class Outlet extends DeconzService.LightsResource {
63
64
  this.values.anyOn = value
64
65
  break
65
66
  case 'on':
67
+ if (this.values.wallSwitch && !state.reachable) {
68
+ if (this.values.on) {
69
+ this.log('not reachable: force On to false')
70
+ }
71
+ this.values.on = false
72
+ break
73
+ }
66
74
  this.values.on = value
67
75
  break
68
76
  default:
@@ -8,7 +8,7 @@
8
8
  const { ApiClient } = require('hb-deconz-tools')
9
9
  const DeconzService = require('../DeconzService')
10
10
 
11
- const { dateToString, HttpError } = ApiClient
11
+ const { dateToString } = ApiClient
12
12
 
13
13
  /**
14
14
  * @memberof DeconzService
@@ -57,12 +57,6 @@ class SensorsResource extends DeconzService {
57
57
  }
58
58
  }
59
59
 
60
- async identify () {
61
- if (this.resource.body.config.alert) {
62
- return this.put('/config', { alert: 'select' })
63
- }
64
- }
65
-
66
60
  updateState (state) {
67
61
  if (state.lastupdated != null) {
68
62
  this.values.lastUpdated = dateToString(state.lastupdated)
@@ -85,13 +79,9 @@ class SensorsResource extends DeconzService {
85
79
  }
86
80
  }
87
81
 
88
- async put (resource, body) {
89
- try {
90
- await this.client.put(this.rpath + resource, body)
91
- } catch (error) {
92
- if (!(error instanceof HttpError)) {
93
- this.warn(error)
94
- }
82
+ async identify () {
83
+ if (this.resource.body.config.alert) {
84
+ return this.put('/config', { alert: 'select' })
95
85
  }
96
86
  }
97
87
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const DeconzService = require('.')
8
+ const DeconzService = require('../DeconzService')
9
9
 
10
10
  class Switch extends DeconzService.LightsResource {
11
11
  constructor (accessory, resource, params = {}) {
@@ -20,7 +20,7 @@ class Switch extends DeconzService.LightsResource {
20
20
  : this.resource.body.state.all_on
21
21
  }).on('didSet', (value, fromHomeKit) => {
22
22
  if (fromHomeKit) {
23
- this.put({ on: value })
23
+ this.putState({ on: value })
24
24
  }
25
25
  })
26
26
 
@@ -31,18 +31,19 @@ class Switch extends DeconzService.LightsResource {
31
31
  value: this.resource.body.state.any_on
32
32
  }).on('didSet', (value, fromHomeKit) => {
33
33
  if (fromHomeKit) {
34
- this.put({ on: value })
34
+ this.putState({ on: value })
35
35
  }
36
36
  })
37
37
  }
38
38
 
39
- this.addCharacteristicDelegates()
40
-
41
- this.settings = {
42
- resetTimeout: this.platform.config.resetTimeout,
43
- waitTimeUpdate: this.platform.config.waitTimeUpdate,
44
- wallSwitch: false
39
+ if (this.resource.rtype === 'lights') {
40
+ this.addCharacteristicDelegate({
41
+ key: 'wallSwitch',
42
+ value: false
43
+ })
45
44
  }
45
+
46
+ this.addCharacteristicDelegates()
46
47
  }
47
48
 
48
49
  updateState (state) {
@@ -57,6 +58,13 @@ class Switch extends DeconzService.LightsResource {
57
58
  this.values.anyOn = value
58
59
  break
59
60
  case 'on':
61
+ if (this.values.wallSwitch && !state.reachable) {
62
+ if (this.values.on) {
63
+ this.log('not reachable: force On to false')
64
+ }
65
+ this.values.on = false
66
+ break
67
+ }
60
68
  this.values.on = value
61
69
  break
62
70
  default:
@@ -19,7 +19,11 @@ class Temperature extends DeconzService.SensorsResource {
19
19
  key: 'temperature',
20
20
  Characteristic: this.Characteristics.hap.CurrentTemperature,
21
21
  unit: '°C',
22
- props: { minValue: -40, maxValue: 100, minStep: 0.1 },
22
+ props: {
23
+ minValue: Math.round(resource.body.capabilities?.measured_value?.min ?? -40),
24
+ maxValue: Math.round(resource.body.capabilities?.measured_value?.max ?? 100),
25
+ minStep: 0.1
26
+ },
23
27
  value: 0
24
28
  })
25
29
 
@@ -47,7 +51,9 @@ class Temperature extends DeconzService.SensorsResource {
47
51
  }
48
52
 
49
53
  updateState (state) {
50
- if (state.temperature != null) {
54
+ if (state.measured_value != null) {
55
+ this.values.temperature = Math.round(state.measured_value * 10) / 10
56
+ } else if (state.temperature != null) {
51
57
  this.values.temperature = Math.round(state.temperature / 10) / 10
52
58
  }
53
59
  super.updateState(state)
@@ -0,0 +1,125 @@
1
+ // homebridge-deconz/lib/DeconzService/Valve.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('../DeconzService')
9
+ // const timeout = require('homebridge-lib')
10
+
11
+ class Switch extends DeconzService.LightsResource {
12
+ constructor (accessory, resource, params = {}) {
13
+ params.Service = accessory.Services.hap.Valve
14
+ super(accessory, resource, params)
15
+
16
+ this.addCharacteristicDelegate({
17
+ key: 'active',
18
+ Characteristic: this.Characteristics.hap.Active,
19
+ value: this.capabilities.on
20
+ ? this.resource.body.state.on
21
+ : this.resource.body.state.all_on
22
+ }).on('didSet', async (value, fromHomeKit) => {
23
+ try {
24
+ if (fromHomeKit) {
25
+ await this.put(this.statePath, { on: value === this.Characteristics.hap.Active.ACTIVE })
26
+ }
27
+ if (this.values.active) {
28
+ this.values.inUse = this.Characteristics.hap.InUse.IN_USE
29
+ if (this.values.setDuration > 0) {
30
+ this.values.remainingDuration = this.values.setDuration
31
+ this.autoInActive = new Date().valueOf() + this.values.setDuration * 1000
32
+ this.autoInActiveTimeout = setTimeout(async () => {
33
+ try {
34
+ await this.put(this.statePath, { on: false })
35
+ } catch (error) { this.warn(error) }
36
+ }, this.values.setDuration * 1000)
37
+ }
38
+ } else {
39
+ this.values.inUse = this.Characteristics.hap.InUse.NOT_IN_USE
40
+ if (this.autoInActiveTimeout != null) {
41
+ clearTimeout(this.autoInActiveTimeout)
42
+ delete this.autoInActiveTimeout
43
+ delete this.autoInActive
44
+ this.values.remainingDuration = 0
45
+ }
46
+ }
47
+ } catch (error) { this.warn(error) }
48
+ })
49
+
50
+ this.addCharacteristicDelegate({
51
+ key: 'inUse',
52
+ Characteristic: this.Characteristics.hap.InUse,
53
+ value: this.Characteristics.hap.InUse.NOT_IN_USE
54
+ })
55
+
56
+ this.addCharacteristicDelegate({
57
+ key: 'remainingDuration',
58
+ Characteristic: this.Characteristics.hap.RemainingDuration,
59
+ value: 0,
60
+ props: {
61
+ maxValue: 4 * 3600
62
+ },
63
+ getter: async () => {
64
+ const remaining = this.autoInActive - new Date().valueOf()
65
+ return remaining > 0 ? Math.round(remaining / 1000) : 0
66
+ }
67
+ })
68
+
69
+ this.addCharacteristicDelegate({
70
+ key: 'setDuration',
71
+ Characteristic: this.Characteristics.hap.SetDuration,
72
+ value: 300,
73
+ props: {
74
+ maxValue: 4 * 3600
75
+ }
76
+ })
77
+
78
+ this.addCharacteristicDelegate({
79
+ key: 'valveType',
80
+ Characteristic: this.Characteristics.hap.ValveType,
81
+ value: this.Characteristics.hap.ValveType.GENERIC_VALVE
82
+ })
83
+
84
+ if (this.resource.rtype === 'lights') {
85
+ this.addCharacteristicDelegate({
86
+ key: 'wallSwitch',
87
+ value: false
88
+ })
89
+ }
90
+
91
+ this.addCharacteristicDelegates()
92
+
93
+ this.values.active = this.Characteristics.hap.Active.INACTIVE
94
+ }
95
+
96
+ updateState (state) {
97
+ for (const key in state) {
98
+ const value = state[key]
99
+ this.resource.body.state[key] = value
100
+ switch (key) {
101
+ case 'on':
102
+ if (this.values.wallSwitch && !state.reachable) {
103
+ this.log('not reachable: force Active to false')
104
+ this.values.active = this.Characteristics.hap.Active.INACTIVE
105
+ this.values.inUse = this.Characteristics.hap.InUse.NOT_IN_USE
106
+ break
107
+ }
108
+ // falls through
109
+ case 'all_on':
110
+ this.values.active = value
111
+ ? this.Characteristics.hap.Active.ACTIVE
112
+ : this.Characteristics.hap.Active.INACTIVE
113
+ this.values.inUse = value
114
+ ? this.Characteristics.hap.InUse.IN_USE
115
+ : this.Characteristics.hap.InUse.NOT_IN_USE
116
+ break
117
+ default:
118
+ break
119
+ }
120
+ }
121
+ super.updateState(state)
122
+ }
123
+ }
124
+
125
+ module.exports = Switch
@@ -6,6 +6,7 @@
6
6
  'use strict'
7
7
 
8
8
  const DeconzService = require('../DeconzService')
9
+ const { timeout } = require('homebridge-lib')
9
10
 
10
11
  class WarningDevice extends DeconzService.LightsResource {
11
12
  constructor (accessory, resource, params = {}) {
@@ -33,11 +34,10 @@ class WarningDevice extends DeconzService.LightsResource {
33
34
  body = { alert: 'lselect', ontime: onTime }
34
35
  }
35
36
  }
36
- this.put(body)
37
+ await this.put(this.statePath, body)
37
38
  if (value) {
38
- this.timer = setTimeout(() => {
39
- this.values.on = false
40
- }, onTime * 1000)
39
+ await timeout(onTime * 1000)
40
+ this.values.on = false
41
41
  }
42
42
  }
43
43
  })
@@ -5,8 +5,8 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const { timeout } = require('homebridge-lib')
9
8
  const DeconzService = require('../DeconzService')
9
+ const { timeout } = require('homebridge-lib')
10
10
 
11
11
  class WindowCovering extends DeconzService.LightsResource {
12
12
  constructor (accessory, resource, params = {}) {
@@ -34,7 +34,7 @@ class WindowCovering extends DeconzService.LightsResource {
34
34
  return
35
35
  }
36
36
  this.values.targetPosition = Math.round(this.values.targetPosition / 5) * 5
37
- await this.setPosition()
37
+ return this.setPosition()
38
38
  })
39
39
 
40
40
  this.addCharacteristicDelegate({
@@ -46,8 +46,8 @@ class WindowCovering extends DeconzService.LightsResource {
46
46
  this.addCharacteristicDelegate({
47
47
  key: 'holdPosition',
48
48
  Characteristic: this.Characteristics.hap.HoldPosition
49
- }).on('didSet', () => {
50
- this.put({ stop: true })
49
+ }).on('didSet', async () => {
50
+ await this.put(this.statePath, { stop: true })
51
51
  this.values.positionState = this.Characteristics.hap.PositionState.STOPPED
52
52
  })
53
53
 
@@ -60,7 +60,7 @@ class WindowCovering extends DeconzService.LightsResource {
60
60
  return
61
61
  }
62
62
  if (this.values.currentPosition !== 100) {
63
- await this.setPosition()
63
+ return this.setPosition()
64
64
  }
65
65
  })
66
66
  }
@@ -80,7 +80,7 @@ class WindowCovering extends DeconzService.LightsResource {
80
80
  if (!fromHomeKit) {
81
81
  return
82
82
  }
83
- await this.put({ speed: value })
83
+ await this.put('/config', { speed: value })
84
84
  })
85
85
  }
86
86
 
@@ -90,7 +90,7 @@ class WindowCovering extends DeconzService.LightsResource {
90
90
  Characteristic: this.Characteristics.my.PositionChange
91
91
  }).on('didSet', async (value) => {
92
92
  if (value !== 0) {
93
- this.put({ lift_inc: -value })
93
+ await this.put(this.statePath, { lift_inc: -value })
94
94
  await timeout(this.platform.config.waitTimeReset)
95
95
  this.values.positionChange = 0
96
96
  }
@@ -120,7 +120,7 @@ class WindowCovering extends DeconzService.LightsResource {
120
120
  ? this.Characteristics.hap.PositionState.INCREASING
121
121
  : this.Characteristics.hap.PositionState.DECREASING
122
122
  this.moving = new Date()
123
- await this.put({ lift })
123
+ return this.put(this.statePath, { lift })
124
124
  }
125
125
 
126
126
  updateState (state) {
@@ -8,7 +8,7 @@
8
8
  const { ApiClient } = require('hb-deconz-tools')
9
9
  const { ServiceDelegate } = require('homebridge-lib')
10
10
 
11
- const { dateToString } = ApiClient
11
+ const { HttpError, dateToString } = ApiClient
12
12
 
13
13
  /** Service delegates.
14
14
  * @extends ServiceDelegate
@@ -42,6 +42,7 @@ class DeconzService extends ServiceDelegate {
42
42
  static get Switch () { return require('./Switch') }
43
43
  static get Temperature () { return require('./Temperature') }
44
44
  static get Thermostat () { return require('./Thermostat') }
45
+ static get Valve () { return require('./Valve') }
45
46
  static get WarningDevice () { return require('./WarningDevice') }
46
47
  static get WindowCovering () { return require('./WindowCovering') }
47
48
 
@@ -115,6 +116,17 @@ class DeconzService extends ServiceDelegate {
115
116
  }
116
117
  }
117
118
  }
119
+
120
+ async put (path, body) {
121
+ this.debug('PUT %s %j', path, body)
122
+ try {
123
+ await this.client.put(this.rpath + path, body)
124
+ } catch (error) {
125
+ if (!(error instanceof HttpError)) {
126
+ this.warn(error)
127
+ }
128
+ }
129
+ }
118
130
  }
119
131
 
120
132
  module.exports = DeconzService
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": "1.0.0",
7
+ "version": "1.0.2",
8
8
  "keywords": [
9
9
  "homebridge-plugin",
10
10
  "homekit",
@@ -22,13 +22,13 @@
22
22
  "ui": "cli/ui.js"
23
23
  },
24
24
  "engines": {
25
- "deCONZ": "2.22.2",
25
+ "deCONZ": "2.23.1",
26
26
  "homebridge": "^1.6.1",
27
- "node": "18.17.1||^18||^16"
27
+ "node": "20.7.0||^20||^18"
28
28
  },
29
29
  "dependencies": {
30
- "hb-deconz-tools": "~1.0.6",
31
- "homebridge-lib": "~6.4.1"
30
+ "hb-deconz-tools": "~1.0.7",
31
+ "homebridge-lib": "~6.6.0"
32
32
  },
33
33
  "scripts": {
34
34
  "prepare": "standard && rm -rf out && jsdoc -c jsdoc.json",