homebridge-deconz 1.2.12 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/ui.js +1 -49
- package/config.schema.json +0 -5
- package/homebridge-ui/public/index.html +0 -5
- package/homebridge-ui/server.js +0 -1
- package/lib/Deconz/Resource.js +1 -17
- package/lib/DeconzAccessory/Gateway.js +82 -88
- package/lib/DeconzAccessory/index.js +14 -0
- package/lib/DeconzPlatform.js +8 -40
- package/lib/DeconzService/Contact.js +1 -1
- package/lib/DeconzService/DoorLock.js +56 -0
- package/package.json +5 -5
package/cli/ui.js
CHANGED
|
@@ -344,58 +344,10 @@ class Main extends CommandLineTool {
|
|
|
344
344
|
const client = new HttpClient({
|
|
345
345
|
host,
|
|
346
346
|
json: true,
|
|
347
|
+
logger: this,
|
|
347
348
|
name: host,
|
|
348
349
|
timeout: this.clargs.options.timeout
|
|
349
350
|
})
|
|
350
|
-
client
|
|
351
|
-
.on('error', (error) => {
|
|
352
|
-
if (error.request.id !== this.requestId) {
|
|
353
|
-
if (error.request.body == null) {
|
|
354
|
-
this.log(
|
|
355
|
-
'%s: request %d: %s %s', error.request.name, error.request.id,
|
|
356
|
-
error.request.method, error.request.resource
|
|
357
|
-
)
|
|
358
|
-
} else {
|
|
359
|
-
this.log(
|
|
360
|
-
'%s: request %d: %s %s %s', error.request.name, error.request.id,
|
|
361
|
-
error.request.method, error.request.resource, error.request.body
|
|
362
|
-
)
|
|
363
|
-
}
|
|
364
|
-
this.requestId = error.request.id
|
|
365
|
-
}
|
|
366
|
-
this.error('%s: request %d: %s', error.request.name, error.request.id, error)
|
|
367
|
-
})
|
|
368
|
-
.on('request', (request) => {
|
|
369
|
-
if (request.body == null) {
|
|
370
|
-
this.debug(
|
|
371
|
-
'%s: request %d: %s %s', request.name, request.id,
|
|
372
|
-
request.method, request.resource
|
|
373
|
-
)
|
|
374
|
-
this.vdebug(
|
|
375
|
-
'%s: request %d: %s %s', request.name, request.id,
|
|
376
|
-
request.method, request.url
|
|
377
|
-
)
|
|
378
|
-
} else {
|
|
379
|
-
this.debug(
|
|
380
|
-
'%s: request %d: %s %s %s', request.name, request.id,
|
|
381
|
-
request.method, request.resource, request.body
|
|
382
|
-
)
|
|
383
|
-
this.vdebug(
|
|
384
|
-
'%s: request %d: %s %s %s', request.name, request.id,
|
|
385
|
-
request.method, request.url, request.body
|
|
386
|
-
)
|
|
387
|
-
}
|
|
388
|
-
})
|
|
389
|
-
.on('response', (response) => {
|
|
390
|
-
this.vdebug(
|
|
391
|
-
'%s: request %d: response: %j', response.request.name, response.request.id,
|
|
392
|
-
response.body
|
|
393
|
-
)
|
|
394
|
-
this.debug(
|
|
395
|
-
'%s: request %d: %d %s', response.request.name, response.request.id,
|
|
396
|
-
response.statusCode, response.statusMessage
|
|
397
|
-
)
|
|
398
|
-
})
|
|
399
351
|
const response = await client.get('/ping')
|
|
400
352
|
if (response.body !== 'pong') {
|
|
401
353
|
throw new Error(`${host}: cannot ping`)
|
package/config.schema.json
CHANGED
|
@@ -14,10 +14,6 @@
|
|
|
14
14
|
"required": true,
|
|
15
15
|
"default": "deCONZ"
|
|
16
16
|
},
|
|
17
|
-
"forceHttp": {
|
|
18
|
-
"description": "Use plain http instead of https.",
|
|
19
|
-
"type": "boolean"
|
|
20
|
-
},
|
|
21
17
|
"hosts": {
|
|
22
18
|
"title": "Gateways",
|
|
23
19
|
"type": "array",
|
|
@@ -94,7 +90,6 @@
|
|
|
94
90
|
"title": "Advanced Settings",
|
|
95
91
|
"description": "Don't change these, unless you understand what you're doing.",
|
|
96
92
|
"items": [
|
|
97
|
-
"forceHttp",
|
|
98
93
|
"noResponse",
|
|
99
94
|
"parallelRequests",
|
|
100
95
|
"stealth",
|
|
@@ -82,10 +82,6 @@ Copyright © 2022-2026 Erik Baauw. All rights reserved.
|
|
|
82
82
|
default: 'deCONZ',
|
|
83
83
|
type: 'string'
|
|
84
84
|
},
|
|
85
|
-
forceHttp: {
|
|
86
|
-
description: "Use plain http instead of https.",
|
|
87
|
-
type: "boolean"
|
|
88
|
-
},
|
|
89
85
|
noResponse: {
|
|
90
86
|
description: "Report unreachable lights as <i>No Response</i> in HomeKit.",
|
|
91
87
|
type: "boolean"
|
|
@@ -147,7 +143,6 @@ Copyright © 2022-2026 Erik Baauw. All rights reserved.
|
|
|
147
143
|
title: "Advanced Settings",
|
|
148
144
|
description: "Don't change these, unless you understand what you're doing.",
|
|
149
145
|
items: [
|
|
150
|
-
"forceHttp",
|
|
151
146
|
"parallelRequests",
|
|
152
147
|
"stealth",
|
|
153
148
|
"timeout",
|
package/homebridge-ui/server.js
CHANGED
package/lib/Deconz/Resource.js
CHANGED
|
@@ -344,7 +344,7 @@ class Resource {
|
|
|
344
344
|
case 'Battery': return 'Battery'
|
|
345
345
|
case 'CarbonMonoxide': return 'CarbonMonoxide'
|
|
346
346
|
case 'Consumption': return 'Consumption'
|
|
347
|
-
|
|
347
|
+
case 'DoorLock': return 'DoorLock'
|
|
348
348
|
case 'Daylight': return 'Daylight'
|
|
349
349
|
case 'DaylightOffset': return ''
|
|
350
350
|
case 'Fire': return 'Smoke'
|
|
@@ -1159,22 +1159,6 @@ class Resource {
|
|
|
1159
1159
|
break
|
|
1160
1160
|
case 'icasa':
|
|
1161
1161
|
switch (this.model) {
|
|
1162
|
-
case 'ICZB-KPD12':
|
|
1163
|
-
case 'ICZB-KPD14S':
|
|
1164
|
-
case 'ICZB-KPD18S':
|
|
1165
|
-
buttons.push([1, 'Off', SINGLE | LONG])
|
|
1166
|
-
buttons.push([2, 'On', SINGLE | LONG])
|
|
1167
|
-
if (this.model !== 'ICZB-KPD12') {
|
|
1168
|
-
buttons.push([3, 'S1', SINGLE])
|
|
1169
|
-
buttons.push([4, 'S2', SINGLE])
|
|
1170
|
-
if (this.model === 'ICZB-KPD18S') {
|
|
1171
|
-
buttons.push([5, 'S3', SINGLE])
|
|
1172
|
-
buttons.push([6, 'S4', SINGLE])
|
|
1173
|
-
buttons.push([7, 'S5', SINGLE])
|
|
1174
|
-
buttons.push([8, 'S6', SINGLE])
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
break
|
|
1178
1162
|
case 'ICZB-RM11S':
|
|
1179
1163
|
buttons.push([1, '1 Off', SINGLE | LONG])
|
|
1180
1164
|
buttons.push([2, '1 On', SINGLE | LONG])
|
|
@@ -21,8 +21,6 @@ import { DeconzService } from '../DeconzService/index.js'
|
|
|
21
21
|
import '../DeconzService/Button.js'
|
|
22
22
|
import '../DeconzService/Gateway.js'
|
|
23
23
|
|
|
24
|
-
const { HttpError } = ApiClient
|
|
25
|
-
|
|
26
24
|
const migration = {
|
|
27
25
|
name: 'homebridge-deconz',
|
|
28
26
|
description: 'migration',
|
|
@@ -78,12 +76,6 @@ class Gateway extends AccessoryDelegate {
|
|
|
78
76
|
if (this.context.settingsById == null) {
|
|
79
77
|
this.context.settingsById = {}
|
|
80
78
|
}
|
|
81
|
-
// if (this.context.fullState != null) {
|
|
82
|
-
// this.analyseFullState(this.context.fullState, {
|
|
83
|
-
// analyseOnly: true,
|
|
84
|
-
// logUnsupported: true
|
|
85
|
-
// })
|
|
86
|
-
// }
|
|
87
79
|
|
|
88
80
|
this.addPropertyDelegate({
|
|
89
81
|
key: 'apiKey',
|
|
@@ -127,6 +119,11 @@ class Gateway extends AccessoryDelegate {
|
|
|
127
119
|
this.pollNext = true
|
|
128
120
|
})
|
|
129
121
|
|
|
122
|
+
this.addPropertyDelegate({
|
|
123
|
+
key: 'fingerprint',
|
|
124
|
+
silent: true
|
|
125
|
+
})
|
|
126
|
+
|
|
130
127
|
this.addPropertyDelegate({
|
|
131
128
|
key: 'heartrate',
|
|
132
129
|
value: 30,
|
|
@@ -137,16 +134,15 @@ class Gateway extends AccessoryDelegate {
|
|
|
137
134
|
key: 'host',
|
|
138
135
|
value: params.host,
|
|
139
136
|
silent: true
|
|
140
|
-
}).on('didSet', (value) => {
|
|
141
|
-
if (this.client != null) {
|
|
142
|
-
this.client.host = value
|
|
143
|
-
}
|
|
144
|
-
if (this.wsClient != null) {
|
|
145
|
-
this.wsClient.host = this.values.host.split(':')[0] +
|
|
146
|
-
':' + this.values.wsPort
|
|
147
|
-
}
|
|
148
137
|
})
|
|
149
138
|
|
|
139
|
+
this.addPropertyDelegate({
|
|
140
|
+
key: 'https',
|
|
141
|
+
value: params.config.https,
|
|
142
|
+
silent: true
|
|
143
|
+
})
|
|
144
|
+
this.values.https = params.config.https
|
|
145
|
+
|
|
150
146
|
this.addPropertyDelegate({
|
|
151
147
|
key: 'periodicEvents',
|
|
152
148
|
value: false,
|
|
@@ -196,16 +192,32 @@ class Gateway extends AccessoryDelegate {
|
|
|
196
192
|
}
|
|
197
193
|
})
|
|
198
194
|
|
|
195
|
+
this.addPropertyDelegate({
|
|
196
|
+
key: 'url',
|
|
197
|
+
silent: true
|
|
198
|
+
}).on('didSet', async (value) => {
|
|
199
|
+
if (value != null) {
|
|
200
|
+
this.log('gateway %s at %s', this.id, value)
|
|
201
|
+
try {
|
|
202
|
+
await this.createClient()
|
|
203
|
+
this.values.wsPort = null
|
|
204
|
+
} catch (error) { this.warn(error) }
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
this.values.url = null
|
|
208
|
+
|
|
199
209
|
this.addPropertyDelegate({
|
|
200
210
|
key: 'wsPort',
|
|
201
|
-
value:
|
|
211
|
+
value: null,
|
|
202
212
|
silent: true
|
|
203
|
-
}).on('didSet', (value) => {
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
213
|
+
}).on('didSet', async (value) => {
|
|
214
|
+
if (value != null) {
|
|
215
|
+
try {
|
|
216
|
+
await this.createWsClient()
|
|
217
|
+
} catch (error) { this.warn(error) }
|
|
207
218
|
}
|
|
208
219
|
})
|
|
220
|
+
this.values.wsPort = null
|
|
209
221
|
|
|
210
222
|
this.log(
|
|
211
223
|
'%s %s gateway v%s', this.values.manufacturer, this.values.model,
|
|
@@ -256,14 +268,13 @@ class Gateway extends AccessoryDelegate {
|
|
|
256
268
|
*/
|
|
257
269
|
this.scheduleServicesByRid = {}
|
|
258
270
|
|
|
259
|
-
this.createClient()
|
|
260
|
-
this.createWsClient()
|
|
261
271
|
this.heartbeatEnabled = true
|
|
262
272
|
this
|
|
263
273
|
.on('identify', this.identify)
|
|
264
|
-
.once('heartbeat', (beat) => {
|
|
274
|
+
.once('heartbeat', async (beat) => {
|
|
275
|
+
this.initialBeat = beat
|
|
276
|
+
})
|
|
265
277
|
.on('heartbeat', this.heartbeat)
|
|
266
|
-
.on('shutdown', this.shutdown)
|
|
267
278
|
}
|
|
268
279
|
|
|
269
280
|
get transitionTime () { return this.service.values.transitionTime }
|
|
@@ -330,13 +341,11 @@ class Gateway extends AccessoryDelegate {
|
|
|
330
341
|
*/
|
|
331
342
|
async found (host, config) {
|
|
332
343
|
try {
|
|
333
|
-
this.values.host = host
|
|
334
344
|
this.context.config = config
|
|
345
|
+
this.values.host = host
|
|
346
|
+
this.values.https = config.https
|
|
335
347
|
this.values.software = config.swversion
|
|
336
|
-
|
|
337
|
-
this.debug('initialising...')
|
|
338
|
-
await this.connect()
|
|
339
|
-
}
|
|
348
|
+
this.values.url = 'http' + (config.https ? 's' : '') + '://' + host
|
|
340
349
|
} catch (error) {
|
|
341
350
|
this.error(error)
|
|
342
351
|
}
|
|
@@ -344,7 +353,7 @@ class Gateway extends AccessoryDelegate {
|
|
|
344
353
|
|
|
345
354
|
async shutdown () {
|
|
346
355
|
this.service.values.statusActive = false
|
|
347
|
-
return this.wsClient
|
|
356
|
+
return this.wsClient?.close()
|
|
348
357
|
}
|
|
349
358
|
|
|
350
359
|
/** Called every second.
|
|
@@ -372,7 +381,9 @@ class Gateway extends AccessoryDelegate {
|
|
|
372
381
|
this.values.firmware = parseInt(config.fwversion.slice(6, 8)) + '.' +
|
|
373
382
|
parseInt(config.fwversion.slice(2, 4), 16) + '.' +
|
|
374
383
|
parseInt(config.fwversion.slice(4, 6), 16)
|
|
375
|
-
this.values.wsPort =
|
|
384
|
+
this.values.wsPort = this.values.https
|
|
385
|
+
? config.websocketport_wss ?? config.websocketport
|
|
386
|
+
: config.websocketport
|
|
376
387
|
this.service.update(config)
|
|
377
388
|
if (this.checkApiKeys) {
|
|
378
389
|
const myEntry = config.whitelist[this.values.apiKey]
|
|
@@ -390,70 +401,47 @@ class Gateway extends AccessoryDelegate {
|
|
|
390
401
|
|
|
391
402
|
/** Create {@link DeconzAccessory.Gateway#client}.
|
|
392
403
|
*/
|
|
393
|
-
createClient () {
|
|
404
|
+
async createClient () {
|
|
405
|
+
this.client?.removeAllListeners()
|
|
394
406
|
/** REST API client for the gateway.
|
|
395
407
|
* @type {DeconzClient}
|
|
396
408
|
*/
|
|
397
409
|
this.client = new ApiClient({
|
|
398
410
|
apiKey: this.values.apiKey,
|
|
399
411
|
config: this.context.config,
|
|
412
|
+
fingerprint: this.values.fingerprint,
|
|
400
413
|
host: this.values.host,
|
|
414
|
+
https: this.values.https,
|
|
415
|
+
logger: this,
|
|
401
416
|
maxSockets: this.platform.config.parallelRequests,
|
|
402
417
|
timeout: this.platform.config.timeout,
|
|
403
418
|
waitTimePut: this.platform.config.waitTimePut,
|
|
404
419
|
waitTimePutGroup: this.platform.config.waitTimePutGroup,
|
|
405
420
|
waitTimeResend: this.platform.config.waitTimeResend
|
|
406
421
|
})
|
|
407
|
-
this.
|
|
408
|
-
.on('error', (error) => {
|
|
409
|
-
if (error instanceof HttpError) {
|
|
410
|
-
if (error.request.id !== this.requestId) {
|
|
411
|
-
this.log(
|
|
412
|
-
'request %d: %s %s%s', error.request.id,
|
|
413
|
-
error.request.method, error.request.resource,
|
|
414
|
-
error.request.body == null ? '' : ' ' + error.request.body
|
|
415
|
-
)
|
|
416
|
-
this.requestId = error.request.id
|
|
417
|
-
}
|
|
418
|
-
this.warn('request %s: %s', error.request.id, error)
|
|
419
|
-
return
|
|
420
|
-
}
|
|
421
|
-
this.warn(error)
|
|
422
|
-
})
|
|
423
|
-
.on('request', (request) => {
|
|
424
|
-
this.debug(
|
|
425
|
-
'request %d: %s %s%s', request.id,
|
|
426
|
-
request.method, request.resource,
|
|
427
|
-
request.body == null ? '' : ' ' + request.body
|
|
428
|
-
)
|
|
429
|
-
this.vdebug(
|
|
430
|
-
'request %s: %s %s%s', request.id,
|
|
431
|
-
request.method, request.url,
|
|
432
|
-
request.body == null ? '' : ' ' + request.body
|
|
433
|
-
)
|
|
434
|
-
})
|
|
435
|
-
.on('response', (response) => {
|
|
436
|
-
this.vdebug(
|
|
437
|
-
'request %d: response: %j', response.request.id,
|
|
438
|
-
response.body
|
|
439
|
-
)
|
|
440
|
-
this.debug(
|
|
441
|
-
'request %s: %d %s', response.request.id,
|
|
442
|
-
response.statusCode, response.statusMessage
|
|
443
|
-
)
|
|
444
|
-
})
|
|
422
|
+
return this.connect()
|
|
445
423
|
}
|
|
446
424
|
|
|
447
425
|
/** Create {@link DeconzAccessory.Gateway#wsclient}.
|
|
448
426
|
*/
|
|
449
|
-
createWsClient () {
|
|
427
|
+
async createWsClient () {
|
|
428
|
+
this.wsRestart = true
|
|
429
|
+
await this.wsClient?.close()
|
|
430
|
+
this.wsClient?.removeAllListeners()
|
|
431
|
+
const wss = this.values.https && this.context.fullState?.config.websocketport_wss != null
|
|
432
|
+
const port = wss
|
|
433
|
+
? this.context.fullState?.config.websocketport_wss
|
|
434
|
+
: this.context.fullState?.config.websocketport
|
|
435
|
+
const options = {
|
|
436
|
+
wss,
|
|
437
|
+
fingerprint: this.values.fingerprint,
|
|
438
|
+
host: this.values.host.split(':')[0] + ':' + port,
|
|
439
|
+
retryTime: 0
|
|
440
|
+
}
|
|
450
441
|
/** Client for gateway web socket notifications.
|
|
451
442
|
* @type {DeconzWsClient}
|
|
452
443
|
*/
|
|
453
|
-
this.wsClient = new WsClient(
|
|
454
|
-
host: this.values.host.split(':')[0] + ':' + this.values.wsPort,
|
|
455
|
-
retryTime: 15
|
|
456
|
-
})
|
|
444
|
+
this.wsClient = new WsClient(options)
|
|
457
445
|
this.wsClient
|
|
458
446
|
.on('error', (error) => {
|
|
459
447
|
this.warn('websocket communication error: %s', error)
|
|
@@ -490,14 +478,13 @@ class Gateway extends AccessoryDelegate {
|
|
|
490
478
|
this.pollFullState = true
|
|
491
479
|
})
|
|
492
480
|
.on('closed', (url, retryTime) => {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
)
|
|
497
|
-
} else {
|
|
498
|
-
this.log('websocket connection to %s closed', url)
|
|
481
|
+
this.log('websocket connection to %s closed', url)
|
|
482
|
+
if (!this.wsRestart) {
|
|
483
|
+
this.values.wsPort = null
|
|
499
484
|
}
|
|
500
485
|
})
|
|
486
|
+
this.wsClient.listen()
|
|
487
|
+
this.wsRestart = false
|
|
501
488
|
}
|
|
502
489
|
|
|
503
490
|
/** Connect to the gateway.
|
|
@@ -515,8 +502,8 @@ class Gateway extends AccessoryDelegate {
|
|
|
515
502
|
if (this.values.apiKey == null) {
|
|
516
503
|
this.values.apiKey =
|
|
517
504
|
await this.client.getApiKey('homebridge-deconz')
|
|
505
|
+
this.values.fingerprint = this.client.fingerprint
|
|
518
506
|
}
|
|
519
|
-
this.wsClient.listen()
|
|
520
507
|
this.service.values.restart = false
|
|
521
508
|
this.service.values.statusActive = true
|
|
522
509
|
this.checkApiKeys = true
|
|
@@ -989,6 +976,9 @@ class Gateway extends AccessoryDelegate {
|
|
|
989
976
|
this.vdebug('%spolling...', this.pollNext ? 'priority ' : '')
|
|
990
977
|
if (this.context.fullState == null || this.pollFullState) {
|
|
991
978
|
const fullState = await this.client.get('/')
|
|
979
|
+
if (this.values.https && (this.values.fingerprint == null || this.values.fingerprint === '')) {
|
|
980
|
+
this.values.fingerprint = this.client.fingerprint
|
|
981
|
+
}
|
|
992
982
|
try {
|
|
993
983
|
fullState.groups[0] = await this.client.get('/groups/0')
|
|
994
984
|
} catch (error) {}
|
|
@@ -1025,6 +1015,9 @@ class Gateway extends AccessoryDelegate {
|
|
|
1025
1015
|
this.warn('deCONZ not ready')
|
|
1026
1016
|
return
|
|
1027
1017
|
}
|
|
1018
|
+
if (this.values.https && (this.values.fingerprint == null || this.values.fingerprint === '')) {
|
|
1019
|
+
this.values.fingerprint = this.client.fingerprint
|
|
1020
|
+
}
|
|
1028
1021
|
this.context.fullState.config = config
|
|
1029
1022
|
this.context.fullState.lights = await this.client.get('/lights')
|
|
1030
1023
|
this.context.fullState.sensors = await this.client.get('/sensors')
|
|
@@ -1338,25 +1331,26 @@ class Gateway extends AccessoryDelegate {
|
|
|
1338
1331
|
const resource = new Deconz.Resource(this, rtype, rid, body)
|
|
1339
1332
|
const { id, serviceName } = resource
|
|
1340
1333
|
// FIX_ME: check introspect against whitelist
|
|
1341
|
-
if (
|
|
1334
|
+
if (resource.body.type === 'ZHASwitch') {
|
|
1342
1335
|
if (!resource.capabilities._introspect) {
|
|
1343
|
-
|
|
1336
|
+
warn(
|
|
1344
1337
|
'%s: /sensors/%d: %s by %s: no introspect',
|
|
1345
1338
|
id, rid, resource.model, resource.manufacturer
|
|
1346
1339
|
)
|
|
1347
1340
|
}
|
|
1348
|
-
}
|
|
1341
|
+
}
|
|
1342
|
+
if (resource.capabilities._buttons != null) {
|
|
1349
1343
|
if (
|
|
1350
1344
|
JSON.stringify(resource.capabilities._buttons) !==
|
|
1351
1345
|
JSON.stringify(resource.capabilities.buttons) ||
|
|
1352
1346
|
resource.capabilities._namespace !== resource.capabilities.namespace
|
|
1353
1347
|
) {
|
|
1354
|
-
|
|
1348
|
+
debug(
|
|
1355
1349
|
'%s: /sensors/%d: %s by %s: whitelist vs introspect mismatch: %j',
|
|
1356
1350
|
id, rid, resource.model, resource.manufacturer, resource.capabilities
|
|
1357
1351
|
)
|
|
1358
1352
|
} else {
|
|
1359
|
-
|
|
1353
|
+
warn(
|
|
1360
1354
|
'%s: /sensors/%d: %s by %s: whitelist matches introspect',
|
|
1361
1355
|
id, rid, resource.model, resource.manufacturer
|
|
1362
1356
|
)
|
|
@@ -158,11 +158,25 @@ class DeconzAccessory extends AccessoryDelegate {
|
|
|
158
158
|
} else if (params.serviceName === 'Battery') {
|
|
159
159
|
service = this.servicesByServiceName.Battery?.[0]
|
|
160
160
|
} else if (params.serviceName === 'Consumption') {
|
|
161
|
+
if (this.servicesByServiceName.Consumption?.[0] != null) {
|
|
162
|
+
this.warn(
|
|
163
|
+
'%s: ignoring additional ZHAConsumption resource - consider splitting the accessory',
|
|
164
|
+
resource.rpath
|
|
165
|
+
)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
161
168
|
service = this.servicesByServiceName.Power?.[0]
|
|
162
169
|
if (service != null) {
|
|
163
170
|
service.addResource(resource)
|
|
164
171
|
}
|
|
165
172
|
} else if (params.serviceName === 'Power') {
|
|
173
|
+
if (this.servicesByServiceName.Power?.[0] != null) {
|
|
174
|
+
this.warn(
|
|
175
|
+
'%s: ignoring additional ZHAPower resource - consider splitting the accessory',
|
|
176
|
+
resource.rpath
|
|
177
|
+
)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
166
180
|
service = this.servicesByServiceName.Consumption?.[0]
|
|
167
181
|
if (service != null) {
|
|
168
182
|
service.addResource(resource)
|
package/lib/DeconzPlatform.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { once } from 'node:events'
|
|
7
7
|
|
|
8
8
|
import { timeout } from 'homebridge-lib'
|
|
9
|
-
import { HttpClient } from 'homebridge-lib/HttpClient'
|
|
10
9
|
import { OptionParser } from 'homebridge-lib/OptionParser'
|
|
11
10
|
import { Platform } from 'homebridge-lib/Platform'
|
|
12
11
|
|
|
@@ -29,7 +28,6 @@ class DeconzPlatform extends Platform {
|
|
|
29
28
|
|
|
30
29
|
parseConfigJson (configJson) {
|
|
31
30
|
this.config = {
|
|
32
|
-
forceHttp: false,
|
|
33
31
|
hosts: [],
|
|
34
32
|
noResponse: false,
|
|
35
33
|
parallelRequests: 10,
|
|
@@ -48,7 +46,6 @@ class DeconzPlatform extends Platform {
|
|
|
48
46
|
})
|
|
49
47
|
.stringKey('name')
|
|
50
48
|
.stringKey('platform')
|
|
51
|
-
.boolKey('forceHttp')
|
|
52
49
|
.stringKey('host')
|
|
53
50
|
.arrayKey('hosts')
|
|
54
51
|
.boolKey('noResponse')
|
|
@@ -69,42 +66,9 @@ class DeconzPlatform extends Platform {
|
|
|
69
66
|
this.config.hosts.push(this.config.host)
|
|
70
67
|
}
|
|
71
68
|
this.discovery = new Discovery({
|
|
72
|
-
|
|
69
|
+
logger: this,
|
|
73
70
|
timeout: this.config.timeout
|
|
74
71
|
})
|
|
75
|
-
this.discovery
|
|
76
|
-
.on('error', (error) => {
|
|
77
|
-
if (error instanceof HttpClient.HttpError) {
|
|
78
|
-
this.log(
|
|
79
|
-
'%s: request %d: %s %s', error.request.name,
|
|
80
|
-
error.request.id, error.request.method, error.request.resource
|
|
81
|
-
)
|
|
82
|
-
this.warn(
|
|
83
|
-
'%s: request %d: %s', error.request.name, error.request.id, error
|
|
84
|
-
)
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
this.warn(error)
|
|
88
|
-
})
|
|
89
|
-
.on('request', (request) => {
|
|
90
|
-
this.debug(
|
|
91
|
-
'%s: request %d: %s %s', request.name,
|
|
92
|
-
request.id, request.method, request.resource
|
|
93
|
-
)
|
|
94
|
-
})
|
|
95
|
-
.on('response', (response) => {
|
|
96
|
-
this.debug(
|
|
97
|
-
'%s: request %d: %d %s', response.request.name,
|
|
98
|
-
response.request.id, response.statusCode, response.statusMessage
|
|
99
|
-
)
|
|
100
|
-
})
|
|
101
|
-
.on('found', (name, id, address) => {
|
|
102
|
-
this.debug('%s: found %s at %s', name, id, address)
|
|
103
|
-
})
|
|
104
|
-
.on('searching', (host) => {
|
|
105
|
-
this.debug('upnp: listening on %s', host)
|
|
106
|
-
})
|
|
107
|
-
.on('searchDone', () => { this.debug('upnp: search done') })
|
|
108
72
|
} catch (error) {
|
|
109
73
|
this.error(error)
|
|
110
74
|
}
|
|
@@ -114,9 +78,11 @@ class DeconzPlatform extends Platform {
|
|
|
114
78
|
const id = config.bridgeid
|
|
115
79
|
if (this.gatewayMap[id] == null) {
|
|
116
80
|
this.gatewayMap[id] = new DeconzAccessory.Gateway(this, { config, host })
|
|
81
|
+
await this.gatewayMap[id].found(host, config)
|
|
82
|
+
await once(this.gatewayMap[id], 'initialised')
|
|
83
|
+
} else {
|
|
84
|
+
await this.gatewayMap[id].found(host, config)
|
|
117
85
|
}
|
|
118
|
-
await this.gatewayMap[id].found(host, config)
|
|
119
|
-
await once(this.gatewayMap[id], 'initialised')
|
|
120
86
|
this.emit('found')
|
|
121
87
|
}
|
|
122
88
|
|
|
@@ -224,7 +190,9 @@ class DeconzPlatform extends Platform {
|
|
|
224
190
|
const configs = await this.discovery.discover()
|
|
225
191
|
const jobs = []
|
|
226
192
|
for (const host in configs) {
|
|
227
|
-
|
|
193
|
+
if (this.config.hosts.length === 0 || this.gatewayMap[configs[host].bridgeid] != null) {
|
|
194
|
+
jobs.push(this.foundGateway(host, configs[host]))
|
|
195
|
+
}
|
|
228
196
|
}
|
|
229
197
|
for (const job of jobs) {
|
|
230
198
|
try {
|
|
@@ -19,7 +19,7 @@ class Contact extends DeconzService.SensorsResource {
|
|
|
19
19
|
Characteristic: this.Characteristics.hap.ContactSensorState
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
// With _Status
|
|
22
|
+
// With _Status Tampered_ Eve thinks the _Door Sensor_ is an Eve Windows Guard
|
|
23
23
|
// (instead of an Eve Door & Window) and won't display it.
|
|
24
24
|
this.addCharacteristicDelegates({ noTampered: true })
|
|
25
25
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// homebridge-deconz/lib/DeconzService/DoorLock.js
|
|
2
|
+
// Copyright © 2022-2026 Erik Baauw. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Homebridge plugin for deCONZ.
|
|
5
|
+
|
|
6
|
+
import { DeconzService } from '../DeconzService/index.js'
|
|
7
|
+
import '../DeconzService/SensorsResource.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @memberof DeconzService
|
|
11
|
+
*/
|
|
12
|
+
class DoorLock extends DeconzService.SensorsResource {
|
|
13
|
+
constructor (accessory, resource, params = {}) {
|
|
14
|
+
params.Service = accessory.Services.hap.LockMechanism
|
|
15
|
+
super(accessory, resource, params)
|
|
16
|
+
|
|
17
|
+
this.addCharacteristicDelegate({
|
|
18
|
+
key: 'currentState',
|
|
19
|
+
Characteristic: this.Characteristics.hap.LockCurrentState
|
|
20
|
+
})
|
|
21
|
+
this.addCharacteristicDelegate({
|
|
22
|
+
key: 'targetState',
|
|
23
|
+
Characteristic: this.Characteristics.hap.LockTargetState
|
|
24
|
+
}).on('didSet', async (value, fromHomeKit) => {
|
|
25
|
+
if (fromHomeKit) {
|
|
26
|
+
await this.put('/config', {
|
|
27
|
+
lock: value === this.Characteristics.hap.LockTargetState.SECURED
|
|
28
|
+
})
|
|
29
|
+
this.locking = new Date()
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
this.addCharacteristicDelegates()
|
|
33
|
+
|
|
34
|
+
this.update(resource.body, resource.rpath)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
updateState (state) {
|
|
38
|
+
if (state.lockstate != null) {
|
|
39
|
+
this.values.currentState = state.lockestate === 'locked'
|
|
40
|
+
? this.Characteristics.hap.LockCurrentState.SECURED
|
|
41
|
+
: this.Characteristics.hap.LockCurrentState.UNSECURED
|
|
42
|
+
}
|
|
43
|
+
super.updateState(state)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
updateConfig (config) {
|
|
47
|
+
if (config.lock != null && new Date() - this.locking >= 5000) {
|
|
48
|
+
this.values.targetLockState = config.lock
|
|
49
|
+
? this.Characteristics.hap.LockTargetState.SECURED
|
|
50
|
+
: this.Characteristics.hap.LockTargetState.UNSECURED
|
|
51
|
+
}
|
|
52
|
+
super.updateConfig(config)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
DeconzService.DoorLock = DoorLock
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"ebaauw"
|
|
8
8
|
],
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.3.1",
|
|
11
11
|
"keywords": [
|
|
12
12
|
"homebridge-plugin",
|
|
13
13
|
"homekit",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
29
|
"deCONZ": "2.32.5",
|
|
30
|
-
"homebridge": "^
|
|
31
|
-
"node": "^24||^22
|
|
30
|
+
"homebridge": "^2.0.1",
|
|
31
|
+
"node": "^24||^22"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"hb-deconz-tools": "~
|
|
35
|
-
"homebridge-lib": "~
|
|
34
|
+
"hb-deconz-tools": "~3.0.3",
|
|
35
|
+
"homebridge-lib": "~8.1.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"prepare": "standard && rm -rf out && jsdoc -c jsdoc.json",
|