homebridge-deconz 0.0.16 → 0.0.17

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.
@@ -81,9 +81,8 @@ class ApiClient extends homebridgeLib.HttpClient {
81
81
  * @return {integer} lux - The value in lux.
82
82
  */
83
83
  static lightLevelToLux (v) {
84
- let lux = v ? Math.pow(10, (v - 1) / 10000) : 0.0001
85
- lux = Math.round(lux * 10000) / 10000
86
- return Math.max(0.0001, Math.min(lux, 100000))
84
+ v = Math.max(0, Math.min(v, 60001))
85
+ return v ? Math.round(Math.pow(10, (v - 1) / 10000) * 10) / 10 : 0.0001
87
86
  }
88
87
 
89
88
  /** Create a new instance of a Deconz.Client.
@@ -21,8 +21,9 @@ const sensorsPrios = [
21
21
  'Power',
22
22
  'Consumption',
23
23
  'Temperature',
24
- 'Presence',
24
+ 'Motion',
25
25
  'OpenClose',
26
+ 'AirPurifier',
26
27
  'Thermostat'
27
28
  ]
28
29
 
@@ -305,6 +306,8 @@ class Resource {
305
306
  }
306
307
  } else { // (this.rtype === 'sensors')
307
308
  switch (this.body.type) {
309
+ case 'CLIPAirPurifier':
310
+ case 'ZHAAirPurifier': return 'AirPurifier'
308
311
  case 'ZHAAirQuality':
309
312
  case 'CLIPAirQuality': return 'AirQuality'
310
313
  case 'ZHAAlarm':
@@ -0,0 +1,38 @@
1
+ // homebridge-deconz/lib/DeconzAccessory/Thermostat.js
2
+ // Copyright © 2022 Erik Baauw. All rights reserved.
3
+ //
4
+ // Homebridge plugin for deCONZ.
5
+
6
+ 'use strict'
7
+
8
+ const DeconzAccessory = require('../DeconzAccessory')
9
+
10
+ class AirPurifier extends DeconzAccessory {
11
+ /** Instantiate a delegate for an accessory corresponding to a device.
12
+ * @param {DeconzAccessory.Gateway} gateway - The gateway.
13
+ * @param {Deconz.Device} device - The device.
14
+ */
15
+ constructor (gateway, device, settings = {}) {
16
+ super(gateway, device, gateway.Accessory.Categories.AIR_PURIFIER)
17
+ this.identify()
18
+
19
+ this.service = this.createService(device.resource, { primaryService: true })
20
+
21
+ for (const subtype in device.resourceBySubtype) {
22
+ const resource = device.resourceBySubtype[subtype]
23
+ if (subtype === device.primary) {
24
+ continue
25
+ }
26
+ this.createService(resource)
27
+ }
28
+
29
+ this.createSettingsService()
30
+
31
+ setImmediate(() => {
32
+ this.debug('initialised')
33
+ this.emit('initialised')
34
+ })
35
+ }
36
+ }
37
+
38
+ module.exports = AirPurifier
@@ -54,12 +54,20 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
54
54
  * devices.
55
55
  * @property {Object} fullState - The gateway's full state, from the
56
56
  * last time the gateway was polled.
57
+ * @property {Object} settings - The persisted settings, maintained through
58
+ * the Homebridge UI.
57
59
  */
58
60
  this.context // eslint-disable-line no-unused-expressions
59
61
  this.context.host = params.host
60
62
  this.context.config = params.config
61
- if (this.context.blacklist == null) {
62
- this.context.blacklist = {}
63
+ if (this.context.settingsById == null) {
64
+ this.context.settingsById = {}
65
+ // migration
66
+ for (const id in this.context.blacklist) {
67
+ this.context.settingsById[id] = { expose: false }
68
+ }
69
+ delete this.context.blacklist
70
+ // end migration
63
71
  }
64
72
  if (this.context.fullState != null) {
65
73
  this.analyseFullState(this.context.fullState, { analyseOnly: true })
@@ -217,9 +225,9 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
217
225
  '%d accessories with expose errors: %j', exposeErrors.length,
218
226
  exposeErrors
219
227
  )
220
- const blacklist = Object.keys(this.context.blacklist).sort()
228
+ const settings = Object.keys(this.context.settingsById).sort()
221
229
  this.vdebug(
222
- 'blacklist: %d devices: %j', blacklist.length, blacklist)
230
+ 'settings: %d devices: %j', settings.length, settings)
223
231
  }
224
232
  }
225
233
 
@@ -465,7 +473,14 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
465
473
  this.deleteService(id)
466
474
  }
467
475
  this.exposeErrors = {}
468
- this.context.blacklist = {}
476
+ this.context.settingsById = {}
477
+ this.context.settingsById[this.id] = {
478
+ autoExposeGroups: false,
479
+ autoExposeLights: false,
480
+ autoExposeSensors: false,
481
+ autoExposeSchedules: false,
482
+ logLevel: 2
483
+ }
469
484
  this.context.fullState = null
470
485
  this.context.migration = null
471
486
  this.service.values.lights = false
@@ -494,11 +509,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
494
509
  if (this.deviceById[id] == null) {
495
510
  throw new RangeError(`${id}: unknown device ID`)
496
511
  }
497
- if (expose) {
498
- delete this.context.blacklist[id]
499
- } else {
500
- this.context.blacklist[id] = true
501
- }
512
+ this.context.settingsById[id].expose = expose
502
513
  this.pollNext = true
503
514
  }
504
515
 
@@ -614,7 +625,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
614
625
  name: body.name + ' Settings',
615
626
  subtype: id,
616
627
  resource: rpaths.join(', '),
617
- expose: this.context.blacklist[id] == null
628
+ expose: this.context.settingsById[id].expose
618
629
  })
619
630
  this.serviceById[id] = service
620
631
  }
@@ -854,7 +865,7 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
854
865
  if (
855
866
  this.deviceById[id] == null
856
867
  ) {
857
- delete this.context.blacklist[id]
868
+ delete this.context.settingsById[id]
858
869
  this.deleteAccessory(id)
859
870
  this.deleteService(id)
860
871
  changed = true
@@ -888,7 +899,10 @@ class Gateway extends homebridgeLib.AccessoryDelegate {
888
899
  for (const rid of rids) {
889
900
  try {
890
901
  const { id } = this.deviceByRidByRtype[rtype][rid]
891
- if (this.context.blacklist[id] == null) {
902
+ if (this.context.settingsById[id] == null) {
903
+ this.context.settingsById[id] = { expose: true }
904
+ }
905
+ if (this.context.settingsById[id].expose) {
892
906
  if (this.accessoryById[id] == null) {
893
907
  this.addAccessory(id)
894
908
  changed = true
@@ -34,6 +34,9 @@ class Motion extends DeconzAccessory {
34
34
  this, {},
35
35
  this.service.characteristicDelegate('motion'),
36
36
  this.service.characteristicDelegate('lastActivation'),
37
+ this.serviceByServiceName.LightLevel == null
38
+ ? null
39
+ : this.serviceByServiceName.LightLevel.characteristicDelegate('lightlevel'),
37
40
  this.serviceByServiceName.Temperature == null
38
41
  ? null
39
42
  : this.serviceByServiceName.Temperature.characteristicDelegate('temperature')
@@ -0,0 +1,216 @@
1
+ // homebridge-deconz/lib/DeconzService/AirPurifier.js
2
+ // Copyright © 2022 Erik Baauw. All rights reserved.
3
+ //
4
+ // Homebridge plugin for deCONZ.
5
+
6
+ 'use strict'
7
+
8
+ const DeconzService = require('../DeconzService')
9
+
10
+ class FilterMaintenance extends DeconzService.SensorsResource {
11
+ constructor (accessory, resource, params = {}) {
12
+ params.Service = accessory.Services.hap.FilterMaintenance
13
+ super(accessory, resource, params)
14
+
15
+ this.addCharacteristicDelegate({
16
+ key: 'filterChange',
17
+ Characteristic: this.Characteristics.hap.FilterChangeIndication
18
+ })
19
+
20
+ if (
21
+ resource.body.config.filterlifetime !== undefined &&
22
+ resource.body.state.filterruntime !== undefined
23
+ ) {
24
+ this.addCharacteristicDelegate({
25
+ key: 'filterLifeLevel',
26
+ Characteristic: this.Characteristics.hap.FilterLifeLevel,
27
+ unit: '%'
28
+ })
29
+ this.addCharacteristicDelegate({
30
+ key: 'resetFilter',
31
+ Characteristic: this.Characteristics.hap.ResetFilterIndication,
32
+ props: { adminOnlyAccess: [this.Characteristic.Access.WRITE] },
33
+ value: 0
34
+ }).on('didSet', async (value, fromHomeKit) => {
35
+ await this.put('/config', { filterlifetime: 6 * 30 * 24 * 60 })
36
+ })
37
+ this.values.filterLifeTime = resource.body.config.filterlifetime
38
+ }
39
+
40
+ this.update(resource.body)
41
+ }
42
+
43
+ updateState (state) {
44
+ if (this.values.filterLifeTime != null && state.filterruntime != null) {
45
+ this.values.filterLifeLevel = 100 - Math.round(
46
+ 100 * state.filterruntime / this.values.filterLifeTime
47
+ )
48
+ }
49
+ if (state.replacefilter != null) {
50
+ this.values.filterChange = state.filterChange
51
+ ? this.Characteristics.hap.FilterChangeIndication.CHANGE_FILTER
52
+ : this.Characteristics.hap.FilterChangeIndication.FILTER_OK
53
+ }
54
+ }
55
+
56
+ updateConfig (config) {
57
+ if (config.filterlifetime != null) {
58
+ this.values.filterLifeTime = config.filterlifetime
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @memberof DeconzService
65
+ */
66
+ class AirPurifier extends DeconzService.SensorsResource {
67
+ constructor (accessory, resource, params = {}) {
68
+ params.Service = accessory.Services.hap.AirPurifier
69
+ super(accessory, resource, params)
70
+
71
+ this.addCharacteristicDelegate({
72
+ key: 'active',
73
+ Characteristic: this.Characteristics.hap.Active
74
+ }).on('didSet', async (value, fromHomeKit) => {
75
+ if (fromHomeKit) {
76
+ await this.put('/config', { mode: this.modeValue(value) })
77
+ }
78
+ })
79
+
80
+ this.addCharacteristicDelegate({
81
+ key: 'currentState',
82
+ Characteristic: this.Characteristics.hap.CurrentAirPurifierState
83
+ })
84
+
85
+ this.addCharacteristicDelegate({
86
+ key: 'targetState',
87
+ Characteristic: this.Characteristics.hap.TargetAirPurifierState
88
+ }).on('didSet', async (value, fromHomeKit) => {
89
+ if (fromHomeKit) {
90
+ await this.put('/config', { mode: this.modeValue(null, value) })
91
+ }
92
+ })
93
+
94
+ this.addCharacteristicDelegate({
95
+ key: 'rotationSpeed',
96
+ Characteristic: this.Characteristics.hap.RotationSpeed,
97
+ unit: '%'
98
+ }).on('didSet', async (value, fromHomeKit) => {
99
+ if (fromHomeKit) {
100
+ await this.put('/config', { mode: this.modeValue(null, null, value) })
101
+ }
102
+ })
103
+
104
+ if (resource.body.state.airquality !== undefined) {
105
+ this.airQualityService = new DeconzService.AirQuality(accessory, resource, {
106
+ linkedServiceDelegate: this
107
+ })
108
+ }
109
+
110
+ if (resource.body.state.replacefilter !== undefined) {
111
+ this.filterService = new FilterMaintenance(accessory, resource, {
112
+ linkedServiceDelegate: this
113
+ })
114
+ }
115
+
116
+ if (resource.body.state.deviceruntime !== undefined) {
117
+ // TODO
118
+ }
119
+
120
+ if (resource.body.config.ledindication !== undefined) {
121
+ // TODO
122
+ }
123
+
124
+ if (resource.body.config.locked !== undefined) {
125
+ this.addCharacteristicDelegate({
126
+ key: 'lockPhysicalControls',
127
+ Characteristic: this.Characteristics.hap.LockPhysicalControls
128
+ }).on('didSet', async (value, fromHomeKit) => {
129
+ if (fromHomeKit) {
130
+ await this.put('/config', {
131
+ locked: value === this.Characteristics.hap.LockPhysicalControls
132
+ .CONTROL_LOCK_ENABLED
133
+ })
134
+ }
135
+ })
136
+ }
137
+
138
+ super.addCharacteristicDelegates()
139
+
140
+ this.update(resource.body)
141
+ }
142
+
143
+ modeValue (
144
+ active = this.values.active,
145
+ targetState = this.values.targetState,
146
+ rotationSpeed = this.values.rotationSpeed
147
+ ) {
148
+ if (active === this.Characteristics.hap.Active.INACTIVE) {
149
+ return 'off'
150
+ }
151
+ if (
152
+ targetState === this.Characteristics.hap.TargetAirPurifierState.AUTO ||
153
+ rotationSpeed === 0
154
+ ) {
155
+ return 'auto'
156
+ }
157
+ return 'speed_' + Math.round(rotationSpeed / 20)
158
+ }
159
+
160
+ updateState (state) {
161
+ if (this.values.filterLifeTime != null && state.filterruntime != null) {
162
+ this.values.filterLifeLevel = 100 - Math.round(
163
+ 100 * state.filterruntime / this.values.filterLifeTime
164
+ )
165
+ }
166
+ if (state.replacefilter != null) {
167
+ this.values.filterChange = state.filterChange
168
+ ? this.Characteristics.hap.FilterChangeIndication.CHANGE_FILTER
169
+ : this.Characteristics.hap.FilterChangeIndication.FILTER_OK
170
+ }
171
+ if (state.speed != null) {
172
+ this.values.active = state.speed > 0
173
+ ? this.Characteristics.hap.Active.ACTIVE
174
+ : this.Characteristics.hap.Active.INACTIVE
175
+ this.values.currentState = state.speed === 0
176
+ ? this.Characteristics.hap.CurrentAirPurifierState.INACTIVE
177
+ : this.Characteristics.hap.CurrentAirPurifierState.PURIFYING_AIR
178
+ this.values.rotationSpeed = state.speed
179
+ }
180
+ super.updateState(state)
181
+ if (this.airQualityService != null) {
182
+ this.airQualityService.updateState(state)
183
+ }
184
+ if (this.filterService != null) {
185
+ this.filterService.updateState(state)
186
+ }
187
+ }
188
+
189
+ updateConfig (config) {
190
+ if (config.filterlifetime != null) {
191
+ this.values.filterLifeTime = config.filterlifetime
192
+ }
193
+ if (config.ledindication != null) {
194
+ // TODO
195
+ }
196
+ if (config.locked != null) {
197
+ this.values.lockPhysicalControls = config.locked
198
+ ? this.Characteristics.hap.LockPhysicalControls.CONTROL_LOCK_ENABLED
199
+ : this.Characteristics.hap.LockPhysicalControls.CONTROL_LOCK_DISABLED
200
+ }
201
+ if (config.mode != null) {
202
+ this.values.targetState = config.mode === 'auto'
203
+ ? this.Characteristics.hap.TargetAirPurifierState.AUTO
204
+ : this.Characteristics.hap.TargetAirPurifierState.MANUAL
205
+ }
206
+ super.updateConfig(config)
207
+ if (this.airQualityService != null) {
208
+ this.airQualityService.updateConfig(config)
209
+ }
210
+ if (this.filterService != null) {
211
+ this.filterService.updateConfig(config)
212
+ }
213
+ }
214
+ }
215
+
216
+ module.exports = AirPurifier
@@ -20,14 +20,27 @@ class AirQuality extends DeconzService.SensorsResource {
20
20
  Characteristic: this.Characteristics.hap.AirQuality
21
21
  })
22
22
 
23
- this.addCharacteristicDelegate({
24
- key: 'vocDensity',
25
- Characteristic: this.Characteristics.hap.VOCDensity,
26
- unit: ' µg/m³',
27
- props: { minValue: 0, maxValue: 65535, minStep: 1 }
28
- })
23
+ if (resource.body.state.airqualityppb !== undefined) {
24
+ this.addCharacteristicDelegate({
25
+ key: 'vocDensity',
26
+ Characteristic: this.Characteristics.hap.VOCDensity,
27
+ unit: ' µg/m³',
28
+ props: { minValue: 0, maxValue: 65535, minStep: 1 }
29
+ })
30
+ }
29
31
 
30
- super.addCharacteristicDelegates()
32
+ if (resource.body.state.pm2_5 !== undefined) {
33
+ this.addCharacteristicDelegate({
34
+ key: 'pm2_5Density',
35
+ Characteristic: this.Characteristics.hap.PM2_5Density,
36
+ unit: ' µg/m³',
37
+ props: { minValue: 0, maxValue: 65535, minStep: 1 }
38
+ })
39
+ }
40
+
41
+ if (params.linkedServiceDelegate == null) {
42
+ super.addCharacteristicDelegates()
43
+ }
31
44
 
32
45
  this.update(resource.body)
33
46
  }
@@ -56,6 +69,9 @@ class AirQuality extends DeconzService.SensorsResource {
56
69
  if (state.airqualityppb != null) {
57
70
  this.values.vocDensity = Math.round(state.airqualityppb * 4.57)
58
71
  }
72
+ if (state.pm2_5 != null) {
73
+ this.values.pm2_5Density = state.pm2_5
74
+ }
59
75
  super.updateState(state)
60
76
  }
61
77
  }
@@ -45,6 +45,8 @@ class Contact extends DeconzService.SensorsResource {
45
45
  })
46
46
 
47
47
  this.addCharacteristicDelegates({ noTampered: true })
48
+
49
+ this.update(resource.body)
48
50
  }
49
51
 
50
52
  updateState (state) {
@@ -35,6 +35,8 @@ class LightLevel extends DeconzService.SensorsResource {
35
35
  })
36
36
 
37
37
  this.addCharacteristicDelegates()
38
+
39
+ this.update(resource.body)
38
40
  }
39
41
 
40
42
  updateState (state) {
@@ -15,6 +15,7 @@ const { dateToString } = Deconz.ApiClient
15
15
  */
16
16
  class DeconzService extends homebridgeLib.ServiceDelegate {
17
17
  static get AirPressure () { return require('./AirPressure') }
18
+ static get AirPurifier () { return require('./AirPurifier') }
18
19
  static get AirQuality () { return require('./AirQuality') }
19
20
  static get Alarm () { return require('./Alarm') }
20
21
  static get Battery () { return require('./Battery') }
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.0.16",
7
+ "version": "0.0.17",
8
8
  "keywords": [
9
9
  "homebridge-plugin",
10
10
  "homekit",
@@ -20,14 +20,14 @@
20
20
  "deconz": "cli/deconz.js"
21
21
  },
22
22
  "engines": {
23
- "deCONZ": "2.15.3",
24
- "homebridge": "^1.4.0",
25
- "node": "^16.14.2"
23
+ "deCONZ": "2.16.1",
24
+ "homebridge": "^1.4.1",
25
+ "node": "^16.15.1"
26
26
  },
27
27
  "dependencies": {
28
- "homebridge-lib": "~5.5.0",
28
+ "homebridge-lib": "~5.6.0",
29
29
  "semver": "^7.3.7",
30
- "ws": "^8.5.0",
30
+ "ws": "^8.8.0",
31
31
  "xml2js": "~0.4.23"
32
32
  },
33
33
  "scripts": {