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.
- package/lib/Deconz/ApiClient.js +2 -3
- package/lib/Deconz/Resource.js +4 -1
- package/lib/DeconzAccessory/AirPurifier.js +38 -0
- package/lib/DeconzAccessory/Gateway.js +27 -13
- package/lib/DeconzAccessory/Motion.js +3 -0
- package/lib/DeconzService/AirPurifier.js +216 -0
- package/lib/DeconzService/AirQuality.js +23 -7
- package/lib/DeconzService/Contact.js +2 -0
- package/lib/DeconzService/LightLevel.js +2 -0
- package/lib/DeconzService/index.js +1 -0
- package/package.json +6 -6
package/lib/Deconz/ApiClient.js
CHANGED
@@ -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
|
-
|
85
|
-
|
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.
|
package/lib/Deconz/Resource.js
CHANGED
@@ -21,8 +21,9 @@ const sensorsPrios = [
|
|
21
21
|
'Power',
|
22
22
|
'Consumption',
|
23
23
|
'Temperature',
|
24
|
-
'
|
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.
|
62
|
-
this.context.
|
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
|
228
|
+
const settings = Object.keys(this.context.settingsById).sort()
|
221
229
|
this.vdebug(
|
222
|
-
'
|
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.
|
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
|
-
|
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.
|
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.
|
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.
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
}
|
@@ -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.
|
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.
|
24
|
-
"homebridge": "^1.4.
|
25
|
-
"node": "^16.
|
23
|
+
"deCONZ": "2.16.1",
|
24
|
+
"homebridge": "^1.4.1",
|
25
|
+
"node": "^16.15.1"
|
26
26
|
},
|
27
27
|
"dependencies": {
|
28
|
-
"homebridge-lib": "~5.
|
28
|
+
"homebridge-lib": "~5.6.0",
|
29
29
|
"semver": "^7.3.7",
|
30
|
-
"ws": "^8.
|
30
|
+
"ws": "^8.8.0",
|
31
31
|
"xml2js": "~0.4.23"
|
32
32
|
},
|
33
33
|
"scripts": {
|